home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 97 / CD-ROM 97 / CD-ROM 97.iso / internet / ghostzilla / ghsetup.exe / chrome / comm.jar / content / editor / editor.js < prev    next >
Encoding:
JavaScript  |  2002-04-29  |  79.1 KB  |  2,738 lines

  1. /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: NPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Netscape Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/NPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is mozilla.org code.
  16.  *
  17.  * The Initial Developer of the Original Code is 
  18.  * Netscape Communications Corporation.
  19.  * Portions created by the Initial Developer are Copyright (C) 1998-1999
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *    Sammy Ford (sford@swbell.net)
  24.  *    Dan Haddix (dan6992@hotmail.com)
  25.  *    John Ratke (jratke@owc.net)
  26.  *    Ryan Cassin (rcassin@supernova.org)
  27.  *    Daniel Glazman (glazman@netscape.com)
  28.  *
  29.  * Alternatively, the contents of this file may be used under the terms of
  30.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  31.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  32.  * in which case the provisions of the GPL or the LGPL are applicable instead
  33.  * of those above. If you wish to allow use of your version of this file only
  34.  * under the terms of either the GPL or the LGPL, and not to allow others to
  35.  * use your version of this file under the terms of the NPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the notice
  37.  * and other provisions required by the GPL or the LGPL. If you do not delete
  38.  * the provisions above, a recipient may use your version of this file under
  39.  * the terms of any one of the NPL, the GPL or the LGPL.
  40.  *
  41.  * ***** END LICENSE BLOCK ***** */
  42.  
  43. /* Main Composer window UI control */
  44.  
  45. var editorShell;
  46. var documentModified;
  47. var prefAuthorString = "";
  48. var NormalMode = 1;
  49. var PreviewMode = 2;
  50. // These must match enums in nsIEditorShell.idl:
  51. var DisplayModePreview = 0;
  52. var DisplayModeNormal = 1;
  53. var DisplayModeAllTags = 2;
  54. var DisplayModeSource = 3;
  55. var PreviousNonSourceDisplayMode = 1;
  56. var gEditorDisplayMode = 1;  // Normal Editor mode
  57. var WebCompose = false;     // Set true for Web Composer, leave false for Messenger Composer
  58. var docWasModified = false;  // Check if clean document, if clean then unload when user "Opens"
  59. var gContentWindow = 0;
  60. var gSourceContentWindow = 0;
  61. var gHTMLSourceChanged = false;
  62. var gContentWindowDeck;
  63. var gFormatToolbar;
  64. var gFormatToolbarHidden = false;
  65. var gFormatToolbarCollapsed;
  66. var gEditModeBar;
  67. var gNormalModeButton;
  68. var gTagModeButton;
  69. var gSourceModeButton;
  70. var gPreviewModeButton;
  71. var gIsHTMLEditor = false;
  72. var gColorObj = new Object();
  73. var gDefaultTextColor = "";
  74. var gDefaultBackgroundColor = "";
  75. var gCSSPrefListener;
  76. var gPrefs;
  77.  
  78. // These must be kept in synch with the XUL <options> lists
  79. var gFontSizeNames = new Array("xx-small","x-small","small","medium","large","x-large","xx-large");
  80.  
  81. const nsIFilePicker = Components.interfaces.nsIFilePicker;
  82.  
  83. function nsButtonPrefListener()
  84. {
  85.   try {
  86.     var pbi = pref.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
  87.     pbi.addObserver(this.domain, this, false);
  88.   } catch(ex) {
  89.     dump("Failed to observe prefs: " + ex + "\n");
  90.   }
  91. }
  92.  
  93. // implements nsIObserver
  94. nsButtonPrefListener.prototype =
  95. {
  96.   domain: "editor.use_css",
  97.   observe: function(subject, topic, prefName)
  98.   {
  99.     // verify that we're changing a button pref
  100.     if (topic != "nsPref:changed") return;
  101.     if (prefName.substr(0, this.domain.length) != this.domain) return;
  102.  
  103.     var button = document.getElementById("cmd_highlight");
  104.     var mixedObj = new Object();
  105.     if (button) {
  106.       var prefs = GetPrefs();
  107.       var useCSS = prefs.getBoolPref(prefName);
  108.       var htmlEditor = editorShell.editor.QueryInterface(Components.interfaces.nsIHTMLEditor);
  109.       if (useCSS && htmlEditor) {
  110.         button.removeAttribute("disabled");
  111.         var state = htmlEditor.getHighlightColorState(mixedObj);
  112.         button.setAttribute("state", state);
  113.       }      
  114.       else {
  115.         button.setAttribute("disabled", "true");
  116.         button.setAttribute("state", "transparent");
  117.       }
  118.       if (htmlEditor) {
  119.         htmlEditor.isCSSEnabled = useCSS;
  120.       }
  121.     }
  122.   }
  123. }
  124.  
  125. function AfterHighlightColorChange()
  126. {
  127.   var button = document.getElementById("cmd_highlight");
  128.   var mixedObj = new Object();
  129.   if (button) {
  130.     var state = editorShell.editor.QueryInterface(Components.interfaces.nsIHTMLEditor).getHighlightColorState(mixedObj);
  131.     button.setAttribute("state", state);
  132.     onHighlightColorChange();
  133.   }      
  134. }
  135.  
  136. function EditorOnLoad()
  137. {
  138.     // See if argument was passed.
  139.     if ( window.arguments && window.arguments[0] ) {
  140.         // Opened via window.openDialog with URL as argument.
  141.         // Put argument where EditorStartup expects it.
  142.         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
  143.     }
  144.  
  145.     // get default character set if provided
  146.     if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
  147.       if (window.arguments[1].indexOf("charset=") != -1) {
  148.         var arrayArgComponents = window.arguments[1].split("=");
  149.         if (arrayArgComponents) {
  150.           // Put argument where EditorStartup expects it.
  151.           document.getElementById( "args" ).setAttribute("charset", arrayArgComponents[1]);
  152.         }
  153.       }
  154.     }
  155.  
  156.     WebCompose = true;
  157.     window.tryToClose = EditorCanClose;
  158.  
  159.     // Continue with normal startup.
  160.     EditorStartup('html', document.getElementById("content-frame"));
  161. }
  162.  
  163. function TextEditorOnLoad()
  164. {
  165.     // See if argument was passed.
  166.     if ( window.arguments && window.arguments[0] ) {
  167.         // Opened via window.openDialog with URL as argument.
  168.         // Put argument where EditorStartup expects it.
  169.         document.getElementById( "args" ).setAttribute( "value", window.arguments[0] );
  170.     }
  171.     // Continue with normal startup.
  172.     EditorStartup('text', document.getElementById("content-frame"));
  173. }
  174.  
  175. // This should be called by all editor users when they close their window
  176. //  or other similar "done with editor" actions, like recycling a Mail Composer window.
  177. function EditorCleanup()
  178. {
  179.   SwitchInsertCharToAnotherEditorOrClose();
  180. }
  181.  
  182. function PageIsEmptyAndUntouched()
  183. {
  184.   return (editorShell != null) && editorShell.documentIsEmpty && 
  185.          !docWasModified && !gHTMLSourceChanged;
  186. }
  187.  
  188. function IsEditorContentHTML()
  189. {
  190.   return (editorShell.contentsMIMEType == "text/html");
  191. }
  192.  
  193. function IsInHTMLSourceMode()
  194. {
  195.   return (gEditorDisplayMode == DisplayModeSource);
  196. }
  197.  
  198. // are we editing HTML (i.e. neither in HTML source mode, nor editing a text file)
  199. function IsEditingRenderedHTML()
  200. {
  201.     return IsEditorContentHTML() && !IsInHTMLSourceMode();
  202. }
  203.  
  204.  
  205. var DocumentReloadListener =
  206. {
  207.   NotifyDocumentCreated: function() {},
  208.   NotifyDocumentWillBeDestroyed: function() {},
  209.  
  210.   NotifyDocumentStateChanged:function( isNowDirty )
  211.   {
  212.     var charset = editorShell.GetDocumentCharacterSet();
  213.  
  214.     // unregister the listener to prevent multiple callbacks
  215.     editorShell.UnregisterDocumentStateListener( DocumentReloadListener );
  216.  
  217.     // update the META charset with the current presentation charset
  218.     editorShell.SetDocumentCharacterSet(charset);
  219.   }
  220. };
  221.  
  222. function addEditorClickEventListener()
  223. {
  224.   try {
  225.     var bodyelement = GetBodyElement();
  226.     if (bodyelement)
  227.       bodyelement.addEventListener("click", EditorClick, false);
  228.   } catch (e) {}
  229. }
  230.  
  231. var MessageComposeDocumentStateListener =
  232. {
  233.   NotifyDocumentCreated: function()
  234.   {
  235.     addEditorClickEventListener();
  236.   },
  237.  
  238.   NotifyDocumentWillBeDestroyed: function()
  239.   {
  240.     // note that the editorshell seems to be gone at this point 
  241.     // so we don't have a way to remove the click listener.
  242.     // hopefully it is being cleaned up with all listeners
  243.   },
  244.  
  245.   NotifyDocumentStateChanged:function()
  246.   {
  247.   }
  248. };
  249.  
  250. function isPlaintextEditor()
  251. {
  252.   var editorflags;
  253.   try {
  254.     editorflags = window.editorShell.editor.flags;
  255.   } catch(e) {}
  256.  
  257.   return (editorflags & Components.interfaces.nsIPlaintextEditor.eEditorPlaintextMask);
  258. }
  259.  
  260. // This is called when the real editor document is created,
  261. // before it's loaded.
  262. var DocumentStateListener =
  263. {
  264.   NotifyDocumentCreated: function()
  265.   {
  266.     // Call EditorSetDefaultPrefsAndDoctype first so it gets the default author before initing toolbars
  267.     EditorSetDefaultPrefsAndDoctype();
  268.     EditorInitToolbars();
  269.     BuildRecentMenu(true);      // Build the recent files menu and save to prefs
  270.  
  271.     // Just for convenience
  272.     gContentWindow = window._content;
  273.     gContentWindow.focus();
  274.  
  275.     // udpate menu items now that we have an editor to play with
  276.     // Note: This must be AFTER gContentWindow.focus();
  277.     window.updateCommands("create");
  278.  
  279.     if (!("InsertCharWindow" in window))
  280.       window.InsertCharWindow = null;
  281.     
  282.     // We must wait until document is created to get proper Url
  283.     // (Windows may load with local file paths)
  284.     SetSaveAndPublishUI(GetDocumentUrl());
  285.  
  286.     // Add mouse click watcher if right type of editor
  287.     if (!isPlaintextEditor())
  288.       addEditorClickEventListener();
  289.   },
  290.  
  291.     // note that the editorshell seems to be gone at this point 
  292.     // so we don't have a way to remove the click listener.
  293.     // hopefully it is being cleaned up with all listeners
  294.   NotifyDocumentWillBeDestroyed: function() {},
  295.  
  296.   NotifyDocumentStateChanged:function( isNowDirty )
  297.   {
  298.     /* Notify our dirty detector so this window won't be closed if
  299.        another document is opened */
  300.     if (isNowDirty)
  301.       docWasModified = true;
  302.  
  303.     // hack! Should not need this updateCommands, but there is some controller
  304.     //  bug that this works around. ??
  305.     // comment out the following line because it cause 41573 IME problem on Mac
  306.     //gContentWindow.focus();
  307.     window.updateCommands("create");
  308.     window.updateCommands("save");
  309.   }
  310. };
  311.  
  312. function EditorStartup(editorType, editorElement)
  313. {
  314.   gIsHTMLEditor = (editorType == "html");
  315.  
  316.   if (gIsHTMLEditor)
  317.   {
  318.     gSourceContentWindow = document.getElementById("content-source");
  319.  
  320.     gEditModeBar       = document.getElementById("EditModeToolbar");
  321.     gNormalModeButton  = document.getElementById("NormalModeButton");
  322.     gTagModeButton     = document.getElementById("TagModeButton");
  323.     gSourceModeButton  = document.getElementById("SourceModeButton");
  324.     gPreviewModeButton = document.getElementById("PreviewModeButton");
  325.  
  326.     // mark first tab as selected
  327.     document.getElementById("EditModeTabs").selectedTab = gNormalModeButton;
  328.  
  329.     // XUL elements we use when switching from normal editor to edit source
  330.     gContentWindowDeck = document.getElementById("ContentWindowDeck");
  331.     gFormatToolbar = document.getElementById("FormatToolbar");
  332.   }
  333.  
  334.   // store the editor shell in the window, so that child windows can get to it.
  335.   editorShell = editorElement.editorShell;        // this pattern exposes a JS/XBL bug that causes leaks
  336.   editorShell.editorType = editorType;
  337.  
  338.   editorShell.webShellWindow = window;
  339.   editorShell.contentWindow = window._content;
  340.  
  341.   // set up our global prefs object
  342.   GetPrefsService();
  343.  
  344.   // Startup also used by other editor users, such as Message Composer
  345.   EditorSharedStartup();
  346.  
  347.   // Commands specific to the Composer Application window,
  348.   //  (i.e., not embedded editors)
  349.   //  such as file-related commands, HTML Source editing, Edit Modes...
  350.   SetupComposerWindowCommands();
  351.  
  352.   gCSSPrefListener = new nsButtonPrefListener();
  353.  
  354.   // Get url for editor content and load it.
  355.   // the editor gets instantiated by the editor shell when the URL has finished loading.
  356.   var url = document.getElementById("args").getAttribute("value");
  357.   var charset = document.getElementById("args").getAttribute("charset");
  358.   if (charset) editorShell.SetDocumentCharacterSet(charset);
  359.  
  360.   editorShell.LoadUrl(url);
  361. }
  362.  
  363. // This is also called by Message Composer
  364. function EditorSharedStartup()
  365. {
  366.   // Just for convenience
  367.   gContentWindow = window._content;
  368.  
  369.   // set up JS-implemented commands for Text or HTML editing
  370.   switch (editorShell.editorType)
  371.   {
  372.       case "html":
  373.       case "htmlmail":
  374.         SetupHTMLEditorCommands();
  375.         editorShell.contentsMIMEType = "text/html";
  376.         if (editorShell.editorType == "htmlmail")
  377.           editorShell.RegisterDocumentStateListener( MessageComposeDocumentStateListener );
  378.         else
  379.           // add a listener to be called when document is really done loading
  380.           editorShell.RegisterDocumentStateListener( DocumentStateListener );
  381.         break;
  382.  
  383.       case "text":
  384.         // add listener when document is done loading (for toolbar setup)
  385.         editorShell.RegisterDocumentStateListener( DocumentStateListener );
  386.         // continue below
  387.  
  388.       case "textmail":
  389.         SetupTextEditorCommands();
  390.         editorShell.contentsMIMEType = "text/plain";
  391.         break;
  392.  
  393.       default:
  394.         dump("INVALID EDITOR TYPE: "+editorShell.editorType+"\n");
  395.         SetupTextEditorCommands();
  396.         editorShell.contentsMIMEType = "text/plain";
  397.         break;
  398.   }
  399.   var isMac = (GetOS() == gMac);
  400.  
  401.   // Set platform-specific hints for how to select cells
  402.   // Mac uses "Cmd", all others use "Ctrl"
  403.   var tableKey = GetString(isMac ? "XulKeyMac" : "TableSelectKey");
  404.   var dragStr = tableKey+GetString("Drag");
  405.   var clickStr = tableKey+GetString("Click");
  406.  
  407.   var delStr = GetString(isMac ? "Clear" : "Del");
  408.  
  409.   SafeSetAttribute("menu_SelectCell", "acceltext", clickStr);
  410.   SafeSetAttribute("menu_SelectRow", "acceltext", dragStr);
  411.   SafeSetAttribute("menu_SelectColumn", "acceltext", dragStr);
  412.   SafeSetAttribute("menu_SelectAllCells", "acceltext", dragStr);
  413.   // And add "Del" or "Clear"
  414.   SafeSetAttribute("menu_DeleteCellContents", "acceltext", delStr);
  415.  
  416.   // Set text for indent, outdent keybinding
  417.  
  418.   // hide UI that we don't have components for
  419.   RemoveInapplicableUIElements();
  420.  
  421.   gPrefs = GetPrefs();
  422.  
  423.   // Use browser colors as initial values for editor's default colors
  424.   var BrowserColors = GetDefaultBrowserColors();
  425.   if (BrowserColors)
  426.   {
  427.     gDefaultTextColor = BrowserColors.TextColor;
  428.     gDefaultBackgroundColor = BrowserColors.BackgroundColor;
  429.   }
  430.  
  431.   // For new window, no default last-picked colors
  432.   gColorObj.LastTextColor = "";
  433.   gColorObj.LastBackgroundColor = "";
  434.   gColorObj.LastHighlightColor = "";
  435. }
  436.  
  437. // This method is only called by Message composer when recycling a compose window
  438. function EditorResetFontAndColorAttributes()
  439. {
  440.   document.getElementById("cmd_fontFace").setAttribute("state", "");
  441.   window.editorShell.RemoveTextProperty("font", "color");
  442.   window.editorShell.RemoveTextProperty("font", "bgcolor");
  443.   window.editorShell.RemoveTextProperty("font", "size");
  444.   window.editorShell.RemoveTextProperty("small", "");
  445.   window.editorShell.RemoveTextProperty("big", "");
  446.   var bodyelement = GetBodyElement();
  447.   if (bodyelement)
  448.   {
  449.     bodyelement.removeAttribute("text");
  450.     bodyelement.removeAttribute("bgcolor");
  451.     bodyelement.removeAttribute("link");
  452.     bodyelement.removeAttribute("alink");
  453.     bodyelement.removeAttribute("vlink");
  454.     bodyelement.removeAttribute("background");
  455.   }
  456.   gColorObj.LastTextColor = "";
  457.   gColorObj.LastBackgroundColor = "";
  458.   gColorObj.LastHighlightColor = "";
  459.   document.getElementById("cmd_fontColor").setAttribute("state", "");
  460.   document.getElementById("cmd_backgroundColor").setAttribute("state", "");
  461.   UpdateDefaultColors();
  462. }
  463.  
  464. function _EditorNotImplemented()
  465. {
  466.   dump("Function not implemented\n");
  467. }
  468.  
  469. function EditorShutdown()
  470. {
  471.     // nothing to do. editorShell->Shutdown is called by the nsEditorBoxObject
  472. }
  473.  
  474. function SafeSetAttribute(nodeID, attributeName, attributeValue)
  475. {
  476.     var theNode = document.getElementById(nodeID);
  477.     if (theNode)
  478.         theNode.setAttribute(attributeName, attributeValue);
  479. }
  480.  
  481. function DocumentHasBeenSaved()
  482. {
  483.   var fileurl = "";
  484.   try {
  485.     fileurl = GetDocumentUrl();
  486.   } catch (e) {
  487.     return false;
  488.   }
  489.  
  490.   if (!fileurl || IsUrlAboutBlank(fileurl))
  491.     return false;
  492.  
  493.   // We have a file URL already
  494.   return true;
  495. }
  496.  
  497. function CheckAndSaveDocument(command, allowDontSave)
  498. {
  499.   try {
  500.     // if we don't have an editorShell or an editorDocument, bail
  501.     if (!window.editorShell && !window.editorShell.editorDocument)
  502.       return true;
  503.   } catch (e) { return true; }
  504.  
  505.   var document = editorShell.editorDocument;
  506.   if (!editorShell.documentModified && !gHTMLSourceChanged)
  507.     return true;
  508.  
  509.   // call window.focus, since we need to pop up a dialog
  510.   // and therefore need to be visible (to prevent user confusion)
  511.   window.focus();  
  512.  
  513.   var scheme = GetScheme(GetDocumentUrl());
  514.   var doPublish = (scheme && scheme != "file");
  515.  
  516.   var strID;
  517.   switch (command)
  518.   {
  519.     case "cmd_close":
  520.       strID = "BeforeClosing";
  521.       break;
  522.     case "cmd_preview":
  523.       strID = "BeforePreview";
  524.       break;
  525.     case "cmd_editSendPage":
  526.       strID = "SendPageReason";
  527.       break;
  528.     case "cmd_validate":
  529.       strID = "BeforeValidate";
  530.       break;
  531.   }
  532.     
  533.   var reasonToSave = strID ? GetString(strID) : "";
  534.  
  535.   var title = window.editorShell.editorDocument.title;
  536.   if (!title)
  537.     title = GetString("untitled");
  538.  
  539.   var dialogTitle = GetString(doPublish ? "PublishPage" : "SaveDocument");
  540.   var dialogMsg = GetString("SaveFilePrompt");
  541.   dialogMsg = (dialogMsg.replace(/%title%/,title)).replace(/%reason%/,reasonToSave);
  542.  
  543.   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  544.   promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  545.  
  546.   var result = {value:0};
  547.   var promptFlags = promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1;
  548.   var button1Title = null;
  549.   var button3Title = null;
  550.  
  551.   if (doPublish)
  552.   {
  553.     promptFlags += promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0;
  554.     button1Title = GetString("Publish");
  555.     button3Title = GetString("DontPublish");    
  556.   }
  557.   else
  558.   {
  559.     promptFlags += promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0;
  560.   }
  561.  
  562.   // If allowing "Don't..." button, add that
  563.   if (allowDontSave)
  564.     promptFlags += doPublish ?
  565.         (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2)
  566.         : (promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_2);
  567.   
  568.   result = promptService.confirmEx(window, dialogTitle, dialogMsg, promptFlags,
  569.                           button1Title, null, button3Title, null, {value:0});
  570.  
  571.   if (result == 0)
  572.   {
  573.     // Save, but first finish HTML source mode
  574.     if (gHTMLSourceChanged)
  575.       FinishHTMLSource();
  576.  
  577.     if (doPublish)
  578.     {
  579.       // We save the command the user wanted to do in a global
  580.       // and return as if user canceled because publishing is asynchronous
  581.       // This command will be fired when publishing finishes
  582.       gCommandAfterPublishing = command;
  583.       goDoCommand("cmd_publish");
  584.       return false;
  585.     }
  586.  
  587.     // Save to local disk
  588.     var success = SaveDocument(false, false, editorShell.contentsMIMEType);
  589.     return success;
  590.   }
  591.  
  592.   if (result == 2) // "Don't Save"
  593.     return true;
  594.  
  595.   // Default or result == 1 (Cancel)
  596.   return false;
  597. }
  598.  
  599. // --------------------------- File menu ---------------------------
  600.  
  601.  
  602. // used by openLocation. see openLocation.js for additional notes.
  603. function delayedOpenWindow(chrome, flags, url)
  604. {
  605.   dump("setting timeout\n");
  606.   setTimeout("window.openDialog('"+chrome+"','_blank','"+flags+"','"+url+"')", 10);
  607. }
  608.  
  609. function EditorNewPlaintext()
  610. {
  611.   window.openDialog( "chrome://editor/content/TextEditorAppShell.xul",
  612.                      "_blank",
  613.                      "chrome,dialog=no,all",
  614.                      "about:blank");
  615. }
  616.  
  617. // Check for changes to document and allow saving before closing
  618. // This is hooked up to the OS's window close widget (e.g., "X" for Windows)
  619. function EditorCanClose()
  620. {
  621.   // Returns FALSE only if user cancels save action
  622.  
  623.   // "true" means allow "Don't Save" button
  624.   var canClose = CheckAndSaveDocument("cmd_close", true);
  625.  
  626.   // This is our only hook into closing via the "X" in the caption
  627.   //   or "Quit" (or other paths?)
  628.   //   so we must shift association to another
  629.   //   editor or close any non-modal windows now
  630.   if (canClose && "InsertCharWindow" in window && window.InsertCharWindow)
  631.     SwitchInsertCharToAnotherEditorOrClose();
  632.  
  633.   return canClose;
  634. }
  635.  
  636. // --------------------------- View menu ---------------------------
  637.  
  638. function EditorSetDocumentCharacterSet(aCharset)
  639. {
  640.   if(editorShell)
  641.   {
  642.     editorShell.SetDocumentCharacterSet(aCharset);
  643.     var docUrl = GetDocumentUrl();
  644.     if( !IsUrlAboutBlank(docUrl))
  645.     {
  646.       // reloading the document will reverse any changes to the META charset, 
  647.       // we need to put them back in, which is achieved by a dedicated listener
  648.       editorShell.RegisterDocumentStateListener( DocumentReloadListener );
  649.       editorShell.LoadUrl(docUrl);
  650.     }
  651.   }
  652. }
  653.  
  654. // ------------------------------------------------------------------
  655. function updateCharsetPopupMenu(menuPopup)
  656. {
  657.   if(editorShell.documentModified && !editorShell.documentIsEmpty)
  658.   {
  659.     for (var i = 0; i < menuPopup.childNodes.length; i++)
  660.     {
  661.       var menuItem = menuPopup.childNodes[i];
  662.       menuItem.setAttribute('disabled', 'true');
  663.     }
  664.   }
  665. }
  666.  
  667. // --------------------------- Text style ---------------------------
  668.  
  669. function EditorSetTextProperty(property, attribute, value)
  670. {
  671.   editorShell.SetTextProperty(property, attribute, value);
  672.   gContentWindow.focus();
  673. }
  674.  
  675. function onParagraphFormatChange(paraMenuList, commandID)
  676. {
  677.   if (!paraMenuList)
  678.     return;
  679.  
  680.   var commandNode = document.getElementById(commandID);
  681.   var state = commandNode.getAttribute("state");
  682.  
  683.   // force match with "normal"
  684.   if (state == "body")
  685.     state = "";
  686.  
  687.   if (state == "mixed")
  688.   {
  689.     //Selection is the "mixed" ( > 1 style) state
  690.     paraMenuList.selectedItem = null;
  691.     paraMenuList.setAttribute("label",GetString('Mixed'));
  692.   }
  693.   else
  694.   {
  695.     var menuPopup = document.getElementById("ParagraphPopup");
  696.     var menuItems = menuPopup.childNodes;
  697.     for (var i=0; i < menuItems.length; i++)
  698.     {
  699.       var menuItem = menuItems.item(i);
  700.       if ("value" in menuItem && menuItem.value == state)
  701.       {
  702.         paraMenuList.selectedItem = menuItem;
  703.         break;
  704.       }
  705.     }
  706.   }
  707. }
  708.  
  709. function doStatefulCommand(commandID, newState)
  710. {
  711.   var commandNode = document.getElementById(commandID);
  712.   if (commandNode)
  713.       commandNode.setAttribute("state", newState);
  714.   gContentWindow.focus();   // needed for command dispatch to work
  715.   goDoCommand(commandID);
  716. }
  717.  
  718. function onFontFaceChange(fontFaceMenuList, commandID)
  719. {
  720.   var commandNode = document.getElementById(commandID);
  721.   var state = commandNode.getAttribute("state");
  722.  
  723.   if (state == "mixed")
  724.   {
  725.     //Selection is the "mixed" ( > 1 style) state
  726.     fontFaceMenuList.selectedItem = null;
  727.     fontFaceMenuList.setAttribute("label",GetString('Mixed'));
  728.   }
  729.   else
  730.   {
  731.     var menuPopup = document.getElementById("FontFacePopup");
  732.     var menuItems = menuPopup.childNodes;
  733.     for (var i=0; i < menuItems.length; i++)
  734.     {
  735.       var menuItem = menuItems.item(i);
  736.       if (menuItem.getAttribute("label") && ("value" in menuItem && menuItem.value.toLowerCase() == state.toLowerCase()))
  737.       {
  738.         fontFaceMenuList.selectedItem = menuItem;
  739.         break;
  740.       }
  741.     }
  742.   }
  743. }
  744.  
  745. function EditorSelectFontSize()
  746. {
  747.   var select = document.getElementById("FontSizeSelect");
  748.   if (select)
  749.   {
  750.     if (select.selectedIndex == -1)
  751.       return;
  752.  
  753.     EditorSetFontSize(gFontSizeNames[select.selectedIndex]);
  754.   }
  755. }
  756.  
  757. function onFontSizeChange(fontSizeMenulist, commandID)
  758. {
  759.   // If we don't match anything, set to "0 (normal)"
  760.   var newIndex = 2;
  761.   var size = fontSizeMenulist.getAttribute("size");
  762.   if ( size == "mixed")
  763.   {
  764.     // No single type selected
  765.     newIndex = -1;
  766.   }
  767.   else
  768.   {
  769.     for (var i = 0; i < gFontSizeNames.length; i++)
  770.     {
  771.       if( gFontSizeNames[i] == size )
  772.       {
  773.         newIndex = i;
  774.         break;
  775.       }
  776.     }
  777.   }
  778.   if (fontSizeMenulist.selectedIndex != newIndex)
  779.     fontSizeMenulist.selectedIndex = newIndex;
  780. }
  781.  
  782. function EditorSetFontSize(size)
  783. {
  784.   if( size == "0" || size == "normal" ||
  785.       size == "medium" )
  786.   {
  787.     editorShell.RemoveTextProperty("font", "size");
  788.     // Also remove big and small,
  789.     //  else it will seem like size isn't changing correctly
  790.     editorShell.RemoveTextProperty("small", "");
  791.     editorShell.RemoveTextProperty("big", "");
  792.   } else {
  793.     // Temp: convert from new CSS size strings to old HTML size strings
  794.     switch (size)
  795.     {
  796.       case "xx-small":
  797.       case "x-small":
  798.         size = "-2";
  799.         break;
  800.       case "small":
  801.         size = "-1";
  802.         break;
  803.       case "large":
  804.         size = "+1";
  805.         break;
  806.       case "x-large":
  807.         size = "+2";
  808.         break;
  809.       case "xx-large":
  810.         size = "+3";
  811.         break;
  812.     }
  813.     editorShell.SetTextProperty("font", "size", size);
  814.   }
  815.   gContentWindow.focus();
  816. }
  817.  
  818. function initFontFaceMenu(menuPopup)
  819. {
  820.   if (menuPopup)
  821.   {
  822.     var children = menuPopup.childNodes;
  823.     if (!children) return;
  824.  
  825.     var firstHas = new Object;
  826.     var anyHas = new Object;
  827.     var allHas = new Object;
  828.     allHas.value = false;
  829.  
  830.     // we need to set or clear the checkmark for each menu item since the selection
  831.     // may be in a new location from where it was when the menu was previously opened
  832.  
  833.     // Fixed width (second menu item) is special case: old TT ("teletype") attribute
  834.     editorShell.GetTextProperty("tt", "", "", firstHas, anyHas, allHas);
  835.     children[1].setAttribute("checked", allHas.value);
  836.     var fontWasFound = anyHas.value;
  837.  
  838.     // Skip over default, TT, and separator
  839.     for (var i = 3; i < children.length; i++)
  840.     {
  841.       var menuItem = children[i];
  842.       var faceType = menuItem.getAttribute("value");
  843.  
  844.       if (faceType)
  845.       {
  846.         editorShell.GetTextProperty("font", "face", faceType, firstHas, anyHas, allHas);
  847.  
  848.         // Check the item only if all of selection has the face...
  849.         menuItem.setAttribute("checked", allHas.value);
  850.         // ...but remember if ANY part of the selection has it
  851.         fontWasFound |= anyHas.value;
  852.       }
  853.     }
  854.     // Check the default item if no other item was checked
  855.     // note that no item is checked in the case of "mixed" selection
  856.     children[0].setAttribute("checked", !fontWasFound);
  857.   }
  858. }
  859.  
  860. function initFontSizeMenu(menuPopup)
  861. {
  862.   if (menuPopup)
  863.   {
  864.     var children = menuPopup.childNodes;
  865.     if (!children) return;
  866.  
  867.     var firstHas = new Object;
  868.     var anyHas = new Object;
  869.     var allHas = new Object;
  870.     allHas.value = false;
  871.  
  872.     var sizeWasFound = false;
  873.  
  874.     // we need to set or clear the checkmark for each menu item since the selection
  875.     // may be in a new location from where it was when the menu was previously opened
  876.  
  877.     // First 2 items add <small> and <big> tags
  878.     // While it would be better to show the number of levels,
  879.     //  at least this tells user if either of them are set
  880.     var menuItem = children[0];
  881.     if (menuItem)
  882.     {
  883.       editorShell.GetTextProperty("small", "", "", firstHas, anyHas, allHas);
  884.       menuItem.setAttribute("checked", allHas.value);
  885.       sizeWasFound = anyHas.value;
  886.     }
  887.  
  888.     menuItem = children[1];
  889.     if (menuItem)
  890.     {
  891.       editorShell.GetTextProperty("big", "", "", firstHas, anyHas, allHas);
  892.       menuItem.setAttribute("checked", allHas.value);
  893.       sizeWasFound |= anyHas.value;
  894.     }
  895.  
  896.     // Fixed size items start after menu separator
  897.     var menuIndex = 3;
  898.     // Index of the medium (default) item
  899.     var mediumIndex = 5;
  900.  
  901.     // Scan through all supported "font size" attribute values
  902.     for (var i = -2; i <= 3; i++)
  903.     {
  904.       menuItem = children[menuIndex];
  905.  
  906.       // Skip over medium since it'll be set below.
  907.       // If font size=0 is actually set, we'll toggle it off below if
  908.       // we enter this loop in this case.
  909.       if (menuItem && (i != 0))
  910.       {
  911.         var sizeString = (i <= 0) ? String(i) : ("+" + String(i));
  912.         editorShell.GetTextProperty("font", "size", sizeString, firstHas, anyHas, allHas);
  913.         // Check the item only if all of selection has the size...
  914.         menuItem.setAttribute("checked", allHas.value);
  915.         // ...but remember if ANY of of selection had size set
  916.         sizeWasFound |= anyHas.value;
  917.       }
  918.       menuIndex++;
  919.     }
  920.  
  921.     // if no size was found, then check default (medium)
  922.     // note that no item is checked in the case of "mixed" selection
  923.     children[mediumIndex].setAttribute("checked", !sizeWasFound);
  924.   }
  925.  }
  926.  
  927. function onHighlightColorChange()
  928. {
  929.   var commandNode = document.getElementById("cmd_highlight");
  930.   if (commandNode)
  931.   {
  932.     var color = commandNode.getAttribute("state");
  933.     var button = document.getElementById("HighlightColorButton");
  934.     if (button)
  935.     {
  936.       // No color set - get color set on page or other defaults
  937.       if (!color)
  938.         color = "transparent" ;
  939.  
  940.       button.setAttribute("style", "background-color:"+color+" !important");
  941.     }
  942.   }
  943. }
  944. function onFontColorChange()
  945. {
  946.   var commandNode = document.getElementById("cmd_fontColor");
  947.   if (commandNode)
  948.   {
  949.     var color = commandNode.getAttribute("state");
  950.     var button = document.getElementById("TextColorButton");
  951.     if (button)
  952.     {
  953.       // No color set - get color set on page or other defaults
  954.       if (!color)
  955.         color = gDefaultTextColor;
  956.  
  957.       button.setAttribute("style", "background-color:"+color);
  958.     }
  959.   }
  960. }
  961.  
  962. function onBackgroundColorChange()
  963. {
  964.   var commandNode = document.getElementById("cmd_backgroundColor");
  965.   if (commandNode)
  966.   {
  967.     var color = commandNode.getAttribute("state");
  968.     var button = document.getElementById("BackgroundColorButton");
  969.     if (button)
  970.     {
  971.       if (!color)
  972.         color = gDefaultBackgroundColor;
  973.  
  974.       button.setAttribute("style", "background-color:"+color);
  975.     }
  976.   }
  977. }
  978.  
  979. // Call this when user changes text and/or background colors of the page
  980. function UpdateDefaultColors()
  981. {
  982.   var BrowserColors = GetDefaultBrowserColors();
  983.   var bodyelement = GetBodyElement();
  984.   var defTextColor = gDefaultTextColor;
  985.   var defBackColor = gDefaultBackgroundColor;
  986.  
  987.   if (bodyelement)
  988.   {
  989.     var color = bodyelement.getAttribute("text");
  990.     if (color)
  991.       gDefaultTextColor = color;
  992.     else if (BrowserColors)
  993.       gDefaultTextColor = BrowserColors.TextColor;
  994.  
  995.     color = bodyelement.getAttribute("bgcolor");
  996.     if (color)
  997.       gDefaultBackgroundColor = color;
  998.     else if (BrowserColors)
  999.       gDefaultBackgroundColor = BrowserColors.BackgroundColor;
  1000.   }
  1001.  
  1002.   // Trigger update on toolbar
  1003.   if (defTextColor != gDefaultTextColor)
  1004.   {
  1005.     goUpdateCommand("cmd_fontColor");
  1006.     onFontColorChange();
  1007.   }
  1008.   if (defBackColor != gDefaultBackgroundColor)
  1009.   {
  1010.     goUpdateCommand("cmd_backgroundColor");
  1011.     onBackgroundColorChange();
  1012.   }
  1013. }
  1014.  
  1015. function GetBackgroundElementWithColor()
  1016. {
  1017.   gColorObj.Type = "";
  1018.   gColorObj.PageColor = "";
  1019.   gColorObj.TableColor = "";
  1020.   gColorObj.CellColor = "";
  1021.   gColorObj.BackgroundColor = "";
  1022.  
  1023.   var tagNameObj = new Object;
  1024.   var countObj = new Object;
  1025.   var element = window.editorShell.GetSelectedOrParentTableElement(tagNameObj, countObj);
  1026.   if (element && tagNameObj && tagNameObj.value)
  1027.   {
  1028.     gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
  1029.     gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
  1030.     if (tagNameObj.value.toLowerCase() == "td")
  1031.     {
  1032.       gColorObj.Type = "Cell";
  1033.       gColorObj.CellColor = gColorObj.BackgroundColor;
  1034.  
  1035.       // Get any color that might be on parent table
  1036.       var table = GetParentTable(element);
  1037.       gColorObj.TableColor = GetHTMLOrCSSStyleValue(table, "bgcolor", "background-color");
  1038.       gColorObj.TableColor = ConvertRGBColorIntoHEXColor(gColorObj.TableColor);
  1039.     }
  1040.     else
  1041.     {
  1042.       gColorObj.Type = "Table";
  1043.       gColorObj.TableColor = gColorObj.BackgroundColor;
  1044.     }
  1045.   }
  1046.   else
  1047.   {
  1048.     var prefs = GetPrefs();
  1049.     var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css");
  1050.     if (IsCSSPrefChecked && editorShell.editorType == "html")
  1051.     {
  1052.       var selection = window.editorShell.editorSelection;
  1053.       if (selection)
  1054.       {
  1055.         element = selection.focusNode;
  1056.         while (!window.editorShell.NodeIsBlock(element))
  1057.           element = element.parentNode;
  1058.       }
  1059.       else
  1060.       {
  1061.         element = GetBodyElement();
  1062.       }
  1063.     }
  1064.     else
  1065.     {
  1066.       element = GetBodyElement();
  1067.     }
  1068.     if (element)
  1069.     {
  1070.       gColorObj.Type = "Page";
  1071.       gColorObj.BackgroundColor = GetHTMLOrCSSStyleValue(element, "bgcolor", "background-color");
  1072.       if (gColorObj.BackgroundColor == "")
  1073.       {
  1074.         gColorObj.BackgroundColor = "transparent";
  1075.       }
  1076.       else
  1077.       {
  1078.         gColorObj.BackgroundColor = ConvertRGBColorIntoHEXColor(gColorObj.BackgroundColor);
  1079.       }
  1080.       gColorObj.PageColor = gColorObj.BackgroundColor;
  1081.     }
  1082.   }
  1083.   return element;
  1084. }
  1085.  
  1086. function SetSmiley(smileyText)
  1087. {
  1088.   editorShell.InsertText(smileyText);
  1089.  
  1090.   gContentWindow.focus();
  1091. }
  1092.  
  1093. function EditorSelectColor(colorType, mouseEvent)
  1094. {
  1095.   if (!gColorObj)
  1096.     return;
  1097.  
  1098.   // Shift + mouse click automatically applies last color, if available
  1099.   var useLastColor = mouseEvent ? ( mouseEvent.button == 0 && mouseEvent.shiftKey ) : false;
  1100.   var element;
  1101.   var table;
  1102.   var currentColor = "";
  1103.   var commandNode;
  1104.  
  1105.   if (!colorType)
  1106.     colorType = "";
  1107.  
  1108.   if (colorType == "Text")
  1109.   {
  1110.     gColorObj.Type = colorType;
  1111.  
  1112.     // Get color from command node state
  1113.     commandNode = document.getElementById("cmd_fontColor");
  1114.     currentColor = commandNode.getAttribute("state");
  1115.     gColorObj.TextColor = currentColor;
  1116.  
  1117.     if (useLastColor && gColorObj.LastTextColor )
  1118.       gColorObj.TextColor = gColorObj.LastTextColor;
  1119.     else
  1120.       useLastColor = false;
  1121.   }
  1122.   else if (colorType == "Highlight")
  1123.   {
  1124.     gColorObj.Type = colorType;
  1125.  
  1126.     // Get color from command node state
  1127.     commandNode = document.getElementById("cmd_highlight");
  1128.     currentColor = commandNode.getAttribute("state");
  1129.     gColorObj.HighlightColor = currentColor;
  1130.  
  1131.     if (useLastColor && gColorObj.LastHighlightColor )
  1132.       gColorObj.HighlightColor = gColorObj.LastHighlightColor;
  1133.     else
  1134.       useLastColor = false;
  1135.   }
  1136.   else
  1137.   {
  1138.     element = GetBackgroundElementWithColor();
  1139.     if (!element)
  1140.       return;
  1141.  
  1142.     // Get the table if we found a cell
  1143.     if (gColorObj.Type == "Table")
  1144.       table = element;
  1145.     else if (gColorObj.Type == "Cell")
  1146.       table = GetParentTable(element);
  1147.  
  1148.     // Save to avoid resetting if not necessary
  1149.     currentColor = gColorObj.BackgroundColor;
  1150.  
  1151.     if (colorType == "TableOrCell" || colorType == "Cell")
  1152.     {
  1153.       if (gColorObj.Type == "Cell")
  1154.         gColorObj.Type = colorType;
  1155.       else if (gColorObj.Type != "Table")
  1156.         return;
  1157.     }
  1158.     else if (colorType == "Table" && gColorObj.Type == "Page")
  1159.       return;
  1160.  
  1161.     if (colorType == "" && gColorObj.Type == "Cell")
  1162.     {
  1163.       // Using empty string for requested type means
  1164.       //  we can let user select cell or table
  1165.       gColorObj.Type = "TableOrCell";
  1166.     }
  1167.  
  1168.     if (useLastColor && gColorObj.LastBackgroundColor )
  1169.       gColorObj.BackgroundColor = gColorObj.LastBackgroundColor;
  1170.     else
  1171.       useLastColor = false;
  1172.   }
  1173.   // Save the type we are really requesting
  1174.   colorType = gColorObj.Type;
  1175.  
  1176.   if (!useLastColor)
  1177.   {
  1178.     // Avoid the JS warning
  1179.     gColorObj.NoDefault = false;
  1180.  
  1181.     // Launch the ColorPicker dialog
  1182.     // TODO: Figure out how to position this under the color buttons on the toolbar
  1183.     window.openDialog("chrome://editor/content/EdColorPicker.xul", "_blank", "chrome,close,titlebar,modal", "", gColorObj);
  1184.  
  1185.     // User canceled the dialog
  1186.     if (gColorObj.Cancel)
  1187.       return;
  1188.   }
  1189.  
  1190.   if (gColorObj.Type == "Text")
  1191.   {
  1192.     if (currentColor != gColorObj.TextColor)
  1193.     {
  1194.       if (gColorObj.TextColor)
  1195.         window.editorShell.SetTextProperty("font", "color", gColorObj.TextColor);
  1196.       else
  1197.         window.editorShell.RemoveTextProperty("font", "color");
  1198.     }
  1199.     // Update the command state (this will trigger color button update)
  1200.     goUpdateCommand("cmd_fontColor");
  1201.   }
  1202.   else if (gColorObj.Type == "Highlight")
  1203.   {
  1204.     if (currentColor != gColorObj.HighlightColor)
  1205.     {
  1206.       if (gColorObj.HighlightColor)
  1207.         window.editorShell.SetTextProperty("font", "bgcolor", gColorObj.HighlightColor);
  1208.       else
  1209.         window.editorShell.RemoveTextProperty("font", "bgcolor");
  1210.     }
  1211.     // Update the command state (this will trigger color button update)
  1212.     goUpdateCommand("cmd_highlight");
  1213.   }
  1214.   else if (element)
  1215.   {
  1216.     if (gColorObj.Type == "Table")
  1217.     {
  1218.       // Set background on a table
  1219.       // Note that we shouldn't trust "currentColor" because of "TableOrCell" behavior
  1220.       if (table)
  1221.       {
  1222.         var bgcolor = table.getAttribute("bgcolor");
  1223.         if (bgcolor != gColorObj.BackgroundColor)
  1224.         {
  1225.           if (gColorObj.BackgroundColor)
  1226.             window.editorShell.editor.setAttributeOrEquivalent(table, "bgcolor", gColorObj.BackgroundColor);
  1227.           else
  1228.             window.editorShell.editor.removeAttributeOrEquivalent(table, "bgcolor");
  1229.         }
  1230.       }
  1231.     }
  1232.     else if (currentColor != gColorObj.BackgroundColor)
  1233.     {
  1234.       window.editorShell.BeginBatchChanges();
  1235.       window.editorShell.SetBackgroundColor(gColorObj.BackgroundColor);
  1236.       if (gColorObj.Type == "Page" && gColorObj.BackgroundColor)
  1237.       {
  1238.         // Set all page colors not explicitly set,
  1239.         //  else you can end up with unreadable pages
  1240.         //  because viewer's default colors may not be same as page author's
  1241.         var bodyelement = GetBodyElement();
  1242.         if (bodyelement)
  1243.         {
  1244.           var defColors = GetDefaultBrowserColors();
  1245.           if (defColors)
  1246.           {
  1247.             if (!bodyelement.getAttribute("text"))
  1248.               window.editorShell.editor.setAttributeOrEquivalent(bodyelement, "text", defColors.TextColor);
  1249.  
  1250.             // The following attributes have no individual CSS declaration counterparts
  1251.             // Getting rid of them in favor of CSS implies CSS rules management
  1252.             if (!bodyelement.getAttribute("link"))
  1253.               window.editorShell.SetAttribute(bodyelement, "link", defColors.LinkColor);
  1254.  
  1255.             if (!bodyelement.getAttribute("alink"))
  1256.               window.editorShell.SetAttribute(bodyelement, "alink", defColors.LinkColor);
  1257.  
  1258.             if (!bodyelement.getAttribute("vlink"))
  1259.               window.editorShell.SetAttribute(bodyelement, "vlink", defColors.VisitedLinkColor);
  1260.           }
  1261.         }
  1262.       }
  1263.       window.editorShell.EndBatchChanges();
  1264.     }
  1265.  
  1266.     goUpdateCommand("cmd_backgroundColor");
  1267.   }
  1268.   gContentWindow.focus();
  1269. }
  1270.  
  1271. function GetParentTable(element)
  1272. {
  1273.   var node = element;
  1274.   while (node)
  1275.   {
  1276.     if (node.nodeName.toLowerCase() == "table")
  1277.       return node;
  1278.  
  1279.     node = node.parentNode;
  1280.   }
  1281.   return node;
  1282. }
  1283.  
  1284. function GetParentTableCell(element)
  1285. {
  1286.   var node = element;
  1287.   while (node)
  1288.   {
  1289.     if (node.nodeName.toLowerCase() == "td" || node.nodeName.toLowerCase() == "th")
  1290.       return node;
  1291.  
  1292.     node = node.parentNode;
  1293.   }
  1294.   return node;
  1295. }
  1296.  
  1297. function EditorDblClick(event)
  1298. {
  1299.   if (event.target)
  1300.   {
  1301.     // Only bring up properties if clicked on an element or selected link
  1302.     var element;
  1303.     try {
  1304.       element = event.target.QueryInterface(Components.interfaces.nsIDOMElement);
  1305.     } catch (e) {}
  1306.  
  1307.      //  We use "href" instead of "a" to not be fooled by named anchor
  1308.     if (!element)
  1309.       element = editorShell.GetSelectedElement("href");
  1310.  
  1311.     if (element)
  1312.     {
  1313.       goDoCommand("cmd_objectProperties");  
  1314.       event.preventDefault();
  1315.     }
  1316.   }
  1317. }
  1318.  
  1319. function EditorClick(event)
  1320. {
  1321.   if (!event)
  1322.     return;
  1323.  
  1324.   if (event.detail == 2)
  1325.   {
  1326.     EditorDblClick(event);
  1327.     return;
  1328.   }
  1329.  
  1330.   // In Show All Tags Mode,
  1331.   // single click selects entire element,
  1332.   //  except for body and table elements
  1333.   if (event.target && gEditorDisplayMode == DisplayModeAllTags)
  1334.   {
  1335.     try {
  1336.       var element = event.target.QueryInterface( Components.interfaces.nsIDOMElement);
  1337.       if (element)
  1338.       {
  1339.         var name = element.localName.toLowerCase();
  1340.         if (name != "body" && name != "table" &&
  1341.             name != "td" && name != "th" && name != "caption" && name != "tr")
  1342.         {          
  1343.           var htmlEditor = editorShell.editor.QueryInterface(Components.interfaces.nsIHTMLEditor);
  1344.           if (htmlEditor && event.target)
  1345.           {
  1346.             htmlEditor.selectElement(event.target);
  1347.             event.preventDefault();
  1348.           }
  1349.         }
  1350.       }
  1351.     } catch (e) {}
  1352.   }
  1353. }
  1354.  
  1355. /*TODO: We need an oncreate hook to do enabling/disabling for the
  1356.         Format menu. There should be code like this for the
  1357.         object-specific "Properties" item
  1358. */
  1359. // For property dialogs, we want the selected element,
  1360. //  but will accept a parent link, list, or table cell if inside one
  1361. function GetObjectForProperties()
  1362. {
  1363.   var element = editorShell.GetSelectedElement("");
  1364.   if (element)
  1365.     return element;
  1366.  
  1367.   // Find nearest parent of selection anchor node
  1368.   //   that is a link, list, table cell, or table
  1369.  
  1370.   var anchorNode = editorShell.editorSelection.anchorNode;
  1371.   if (!anchorNode) return null;
  1372.   var node;
  1373.   if (anchorNode.firstChild)
  1374.   {
  1375.     // Start at actual selected node
  1376.     var offset = editorShell.editorSelection.anchorOffset;
  1377.     // Note: If collapsed, offset points to element AFTER caret,
  1378.     //  thus node may be null
  1379.     node = anchorNode.childNodes.item(offset);
  1380.   }
  1381.   if (!node)
  1382.     node = anchorNode;
  1383.  
  1384.   while (node)
  1385.   {
  1386.     if (node.nodeName)
  1387.     {
  1388.       var nodeName = node.nodeName.toLowerCase();
  1389.  
  1390.       // Done when we hit the body
  1391.       if (nodeName == "body") break;
  1392.  
  1393.       if ((nodeName == "a" && node.href) ||
  1394.           nodeName == "ol" || nodeName == "ul" || nodeName == "dl" ||
  1395.           nodeName == "td" || nodeName == "th" ||
  1396.           nodeName == "table")
  1397.       {
  1398.         return node;
  1399.       }
  1400.     }
  1401.     node = node.parentNode;
  1402.   }
  1403.   return null;
  1404. }
  1405.  
  1406. function SetEditMode(mode)
  1407. {
  1408.   if (gIsHTMLEditor)
  1409.   {
  1410.     var bodyNode = editorShell.editorDocument.getElementsByTagName("body").item(0);
  1411.     if (!bodyNode)
  1412.     {
  1413.       dump("SetEditMode: We don't have a body node!\n");
  1414.       return;
  1415.     }
  1416.     // Switch the UI mode before inserting contents
  1417.     //   so user can't type in source window while new window is being filled
  1418.     var previousMode = gEditorDisplayMode;
  1419.     if (!SetDisplayMode(mode))
  1420.       return;
  1421.  
  1422.     if (mode == DisplayModeSource)
  1423.     {
  1424.       // Display the DOCTYPE as a non-editable string above edit area
  1425.       var domdoc;
  1426.       try { domdoc = window.editorShell.editorDocument; } catch (e) { dump( e + "\n");}
  1427.       if (domdoc)
  1428.       {
  1429.         var doctypeNode = document.getElementById("doctype-text");
  1430.         var dt = domdoc.doctype;
  1431.         if (doctypeNode)
  1432.         {
  1433.           if (dt)
  1434.           {
  1435.             doctypeNode.removeAttribute("collapsed");
  1436.             var doctypeText = "<!DOCTYPE " + domdoc.doctype.name;
  1437.             if (dt.publicId)
  1438.               doctypeText += " PUBLIC \"" + domdoc.doctype.publicId;
  1439.             if (dt.systemId)
  1440.               doctypeText += " "+"\"" + dt.systemId;
  1441.             doctypeText += "\">"
  1442.             doctypeNode.setAttribute("value", doctypeText);
  1443.           }
  1444.           else
  1445.             doctypeNode.setAttribute("collapsed", "true");
  1446.         }
  1447.       }
  1448.       // Get the entire document's source string
  1449.  
  1450.       var flags = 256; // OutputEncodeEntities;
  1451.  
  1452.       try { 
  1453.         var prettyPrint = gPrefs.getBoolPref("editor.prettyprint");
  1454.         if (prettyPrint)
  1455.           flags |= 2; // OutputFormatted
  1456.  
  1457.       } catch (e) {}
  1458.  
  1459.       var source = editorShell.GetContentsAs("text/html", flags);
  1460.       var start = source.search(/<html/i);
  1461.       if (start == -1) start = 0;
  1462.       gSourceContentWindow.value = source.slice(start);
  1463.       gSourceContentWindow.focus();
  1464.  
  1465.       // Add input handler so we know if user made any changes
  1466.       gSourceContentWindow.addEventListener("input", oninputHTMLSource, false);
  1467.       gHTMLSourceChanged = false;
  1468.     }
  1469.     else if (previousMode == DisplayModeSource)
  1470.     {
  1471.       // Only rebuild document if a change was made in source window
  1472.       if (gHTMLSourceChanged)
  1473.       {
  1474.         editorShell.BeginBatchChanges();
  1475.         try {
  1476.           // We are comming from edit source mode,
  1477.           //   so transfer that back into the document
  1478.           source = gSourceContentWindow.value;
  1479.           editorShell.RebuildDocumentFromSource(source);
  1480.  
  1481.           // Get the text for the <title> from the newly-parsed document
  1482.           // (must do this for proper conversion of "escaped" characters)
  1483.           var title = "";
  1484.           var titlenodelist = window.editorShell.editorDocument.getElementsByTagName("title");
  1485.           if (titlenodelist)
  1486.           {
  1487.             var titleNode = titlenodelist.item(0);
  1488.             if (titleNode && titleNode.firstChild && titleNode.firstChild.data)
  1489.               title = titleNode.firstChild.data;
  1490.           }
  1491.           if (window.editorShell.editorDocument.title != title)
  1492.           {
  1493.             window.editorShell.editorDocument.title = title;
  1494.             ResetWindowTitleWithFilename();
  1495.           }
  1496.  
  1497.           // reset selection to top of doc (wish we could preserve it!)
  1498.           if (bodyNode)
  1499.             editorShell.editorSelection.collapse(bodyNode, 0);
  1500.  
  1501.         } catch (ex) {
  1502.           dump(ex);
  1503.         }
  1504.         editorShell.EndBatchChanges();
  1505.  
  1506.       } else {
  1507.         // We don't need to call this again, so remove handler
  1508.         gSourceContentWindow.removeEventListener("input", oninputHTMLSource, false);
  1509.       }
  1510.       gHTMLSourceChanged = false;
  1511.  
  1512.       // Clear out the string buffers
  1513.       gSourceContentWindow.value = null;
  1514.  
  1515.       gContentWindow.focus();
  1516.     }
  1517.   }
  1518. }
  1519.  
  1520. function oninputHTMLSource()
  1521. {
  1522.   gHTMLSourceChanged = true;
  1523.  
  1524.   // Trigger update of "Save" button
  1525.   goUpdateCommand("cmd_save");
  1526.  
  1527.   // We don't need to call this again, so remove handler
  1528.   gSourceContentWindow.removeEventListener("input", oninputHTMLSource, false);
  1529. }
  1530.  
  1531. function ResetWindowTitleWithFilename()
  1532. {
  1533.   // Calling this resets the "Title [filename]" that we show on window caption
  1534.   window.editorShell.SetDocumentTitle(window.editorShell.GetDocumentTitle());
  1535. }
  1536.  
  1537. function CancelHTMLSource()
  1538. {
  1539.   // Don't convert source text back into the DOM document
  1540.   gSourceContentWindow.value = "";
  1541.   gHTMLSourceChanged = false;
  1542.   SetDisplayMode(PreviousNonSourceDisplayMode);
  1543. }
  1544.  
  1545.  
  1546. function FinishHTMLSource()
  1547. {
  1548.   // Switch edit modes -- converts source back into DOM document
  1549.   SetEditMode(PreviousNonSourceDisplayMode);
  1550. }
  1551.  
  1552. function CollapseItem(id, collapse)
  1553. {
  1554.   var item = document.getElementById(id);
  1555.   if (item)
  1556.   {
  1557.     if(collapse != (item.getAttribute("collapsed") == "true"))
  1558.       item.setAttribute("collapsed", collapse ? "true" : "");
  1559.   }
  1560. }
  1561.  
  1562. function SetDisplayMode(mode)
  1563. {
  1564.   if (gIsHTMLEditor)
  1565.   {
  1566.     // Already in requested mode:
  1567.     //  return false to indicate we didn't switch
  1568.     if (mode == gEditorDisplayMode)
  1569.       return false;
  1570.  
  1571.     gEditorDisplayMode = mode;
  1572.  
  1573.     // Save the last non-source mode so we can cancel source editing easily
  1574.     if (mode != DisplayModeSource)
  1575.       PreviousNonSourceDisplayMode = mode;
  1576.  
  1577.  
  1578.     // Editorshell does the style sheet loading/unloading
  1579.     editorShell.SetDisplayMode(mode);
  1580.  
  1581.     // Set the UI states
  1582.     var selectedTab = null;
  1583.     if (mode == DisplayModePreview) selectedTab = gPreviewModeButton;
  1584.     if (mode == DisplayModeNormal) selectedTab = gNormalModeButton;
  1585.     if (mode == DisplayModeAllTags) selectedTab = gTagModeButton;
  1586.     if (mode == DisplayModeSource) selectedTab = gSourceModeButton;
  1587.     if (selectedTab)
  1588.       document.getElementById("EditModeTabs").selectedItem = selectedTab;
  1589.  
  1590.     if (mode == DisplayModeSource)
  1591.     {
  1592.       // Switch to the sourceWindow (second in the deck)
  1593.       gContentWindowDeck.setAttribute("selectedIndex","1");
  1594.  
  1595.       //Hide the formatting toolbar if not already hidden
  1596.       gFormatToolbarHidden = gFormatToolbar.getAttribute("hidden");
  1597.       if (gFormatToolbarHidden != "true")
  1598.       {
  1599.         gFormatToolbar.setAttribute("hidden", "true");
  1600.       }
  1601.  
  1602.       gSourceContentWindow.focus();
  1603.     }
  1604.     else
  1605.     {
  1606.       // Switch to the normal editor (first in the deck)
  1607.       gContentWindowDeck.setAttribute("selectedIndex","0");
  1608.  
  1609.       // Restore menus and toolbars
  1610.       if (gFormatToolbarHidden != "true")
  1611.       {
  1612.         gFormatToolbar.setAttribute("hidden", gFormatToolbarHidden);
  1613.       }
  1614.  
  1615.       gContentWindow.focus();
  1616.     }
  1617.  
  1618.     // update commands to disable or re-enable stuff
  1619.     window.updateCommands("mode_switch");
  1620.  
  1621.     // We must set check on menu item since toolbar may have been used
  1622.     document.getElementById("viewPreviewMode").setAttribute("checked","false");
  1623.     document.getElementById("viewNormalMode").setAttribute("checked","false");
  1624.     document.getElementById("viewAllTagsMode").setAttribute("checked","false");
  1625.     document.getElementById("viewSourceMode").setAttribute("checked","false");
  1626.  
  1627.     var menuID;
  1628.     switch(mode)
  1629.     {
  1630.       case DisplayModePreview:
  1631.         menuID = "viewPreviewMode";
  1632.         break;
  1633.       case DisplayModeNormal:
  1634.         menuID = "viewNormalMode";
  1635.         break;
  1636.       case DisplayModeAllTags:
  1637.         menuID = "viewAllTagsMode";
  1638.         break;
  1639.       case DisplayModeSource:
  1640.         menuID = "viewSourceMode";
  1641.         break;
  1642.     }
  1643.     if (menuID)
  1644.       document.getElementById(menuID).setAttribute("checked","true");
  1645.  
  1646.     return true;
  1647.   }
  1648.   return false;
  1649. }
  1650.  
  1651. function EditorToggleParagraphMarks()
  1652. {
  1653.   var menuItem = document.getElementById("viewParagraphMarks");
  1654.   if (menuItem)
  1655.   {
  1656.     // Note that the 'type="checbox"' mechanism automatically
  1657.     //  toggles the "checked" state before the oncommand is called,
  1658.     //  so if "checked" is true now, it was just switched to that mode
  1659.     var checked = menuItem.getAttribute("checked");
  1660.     try {
  1661.       editorShell.DisplayParagraphMarks(checked == "true");
  1662.     }
  1663.     catch(e) { return; }
  1664.   }
  1665. }
  1666.  
  1667. function InitPasteAsMenu()
  1668. {
  1669.   var menuItem = document.getElementById("menu_pasteTable")
  1670.   if(menuItem)
  1671.   {
  1672.     menuItem.IsInTable  
  1673.     menuItem.setAttribute("label", GetString(IsInTable() ? "NestedTable" : "Table"));
  1674.    // menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
  1675.   }
  1676.   // TODO: Do enabling based on what is in the clipboard
  1677. }
  1678.  
  1679. function BuildRecentMenu(savePrefs)
  1680. {
  1681.   // Can't do anything if no prefs
  1682.   if (!gPrefs) return;
  1683.  
  1684.   var popup = document.getElementById("menupopup_RecentFiles");
  1685.   if (!popup || !window.editorShell ||
  1686.       !window.editorShell.editorDocument)
  1687.     return;
  1688.  
  1689.   // Delete existing menu
  1690.   while (popup.firstChild)
  1691.     popup.removeChild(popup.firstChild);
  1692.  
  1693.   // Current page is the "0" item in the list we save in prefs,
  1694.   //  but we don't include it in the menu.
  1695.   var curTitle = window.editorShell.editorDocument.title;
  1696.   var curUrl = StripPassword(GetDocumentUrl());
  1697.   var historyCount = 10;
  1698.   try { historyCount = gPrefs.getIntPref("editor.history.url_maximum"); } catch(e) {}
  1699.   var titleArray = new Array(historyCount);
  1700.   var urlArray   = new Array(historyCount);
  1701.   var menuIndex = 1;
  1702.   var arrayIndex = 0;
  1703.   var i;
  1704.   var disableMenu = true;
  1705.  
  1706.   if(!IsUrlAboutBlank(curUrl))
  1707.   {
  1708.     // Always put latest-opened URL at start of array
  1709.     titleArray[0] = curTitle;
  1710.     urlArray[0] = curUrl;
  1711.     arrayIndex = 1;
  1712.   }
  1713.   for (i = 0; i < historyCount; i++)
  1714.   {
  1715.     var title = GetUnicharPref("editor.history_title_"+i);
  1716.     var url = GetUnicharPref("editor.history_url_"+i);
  1717.  
  1718.     // Continue if URL pref is missing because 
  1719.     //  a URL not found during loading may have been removed
  1720.     // Also skip "data:" URL
  1721.     if (!url || GetScheme(url) == "data")
  1722.       continue;
  1723.  
  1724.     // Never show password in menu!
  1725.     url = StripPassword(url);
  1726.  
  1727.     // Skip over current URL
  1728.     if (url != curUrl)
  1729.     {
  1730.  
  1731.       // Build the menu
  1732.       AppendRecentMenuitem(popup, title, url, menuIndex);
  1733.       menuIndex++;
  1734.       disableMenu = false;
  1735.  
  1736.       // Save in array for prefs
  1737.       if (savePrefs && arrayIndex < historyCount)
  1738.       {
  1739.         titleArray[arrayIndex] = title;
  1740.         urlArray[arrayIndex] = url;
  1741.         arrayIndex++;
  1742.       }
  1743.     }
  1744.   }
  1745.  
  1746.   // Now resave the list back to prefs in the new order
  1747.   if (savePrefs)
  1748.   {
  1749.     savePrefs = false;
  1750.     for (i = 0; i < historyCount; i++)
  1751.     {
  1752.       if (!urlArray[i])
  1753.         break;
  1754.       SetUnicharPref("editor.history_title_"+i, titleArray[i]);
  1755.       SetUnicharPref("editor.history_url_"+i, urlArray[i]);
  1756.       savePrefs = true;
  1757.     }
  1758.   }
  1759.   // Force saving to file so next file opened finds these values
  1760.   if (savePrefs) {
  1761.     var prefsService = Components.classes["@mozilla.org/preferences-service;1"]
  1762.                                  .getService(Components.interfaces.nsIPrefService);
  1763.     prefsService.savePrefFile(null);
  1764.   }
  1765.  
  1766.   // Disable menu item if no entries
  1767.   DisableItem("menu_RecentFiles", disableMenu);
  1768. }
  1769.  
  1770. function AppendRecentMenuitem(menupopup, title, url, menuIndex)
  1771. {
  1772.   if (menupopup)
  1773.   {
  1774.     var menuItem = document.createElementNS(XUL_NS, "menuitem");
  1775.     if (menuItem)
  1776.     {
  1777.       var accessKey;
  1778.       if (menuIndex <= 9)
  1779.         accessKey = String(menuIndex);
  1780.       else if (menuIndex == 10)
  1781.         accessKey = "0";
  1782.       else
  1783.         accessKey = " ";
  1784.  
  1785.       var itemString = accessKey+" ";
  1786.  
  1787.       // Show "title [url]" or just the URL
  1788.       if (title)
  1789.       {
  1790.        itemString += title;
  1791.        itemString += " [";
  1792.       }
  1793.       itemString += url;
  1794.       if (title)
  1795.         itemString += "]";
  1796.  
  1797.       menuItem.setAttribute("label", itemString);
  1798.       menuItem.setAttribute("value", url);
  1799.       if (accessKey != " ")
  1800.         menuItem.setAttribute("accesskey", accessKey);
  1801.       menuItem.setAttribute("oncommand", "editPage(getAttribute('value'), window, false)");
  1802.       menupopup.appendChild(menuItem);
  1803.     }
  1804.   }
  1805. }
  1806.  
  1807. function EditorInitFileMenu()
  1808. {
  1809.   // Disable "Save" menuitem when editing remote url. User should use "Save As"
  1810.   var scheme = GetScheme(GetDocumentUrl());
  1811.   if (scheme && scheme != "file")
  1812.     SetElementEnabledById("saveMenuitem", false);
  1813. }
  1814.  
  1815. function EditorInitFormatMenu()
  1816. {
  1817.   try {
  1818.     InitObjectPropertiesMenuitem("objectProperties");
  1819.     InitRemoveStylesMenuitems("removeStylesMenuitem", "removeLinksMenuitem", "removeNamedAnchorsMenuitem");
  1820.   } catch(ex) {}
  1821.   // Set alignment check
  1822. }
  1823.  
  1824. function InitObjectPropertiesMenuitem(id)
  1825. {
  1826.   // Set strings and enable for the [Object] Properties item
  1827.   // Note that we directly do the enabling instead of
  1828.   //  using goSetCommandEnabled since we already have the menuitem
  1829.   var menuItem = document.getElementById(id);
  1830.   if (!menuItem) return null;
  1831.  
  1832.   var element;
  1833.   var menuStr = GetString("AdvancedProperties");
  1834.   var name;
  1835.  
  1836.   if (IsEditingRenderedHTML())
  1837.     element = GetObjectForProperties();
  1838.  
  1839.   if (element && element.nodeName)
  1840.   {
  1841.     var objStr = "";
  1842.     menuItem.setAttribute("disabled", "");
  1843.     name = element.nodeName.toLowerCase();
  1844.     switch (name)
  1845.     {
  1846.       case "img":
  1847.         // Check if img is enclosed in link
  1848.         //  (use "href" to not be fooled by named anchor)
  1849.         if (editorShell.GetElementOrParentByTagName("href", element))
  1850.           objStr = GetString("ImageAndLink");
  1851.         else
  1852.           objStr = GetString("Image");
  1853.         break;
  1854.       case "hr":
  1855.         objStr = GetString("HLine");
  1856.         break;
  1857.       case "table":
  1858.         objStr = GetString("Table");
  1859.         break;
  1860.       case "th":
  1861.         name = "td";
  1862.       case "td":
  1863.         objStr = GetString("TableCell");
  1864.         break;
  1865.       case "ol":
  1866.       case "ul":
  1867.       case "dl":
  1868.         objStr = GetString("List");
  1869.         break;
  1870.       case "li":
  1871.         objStr = GetString("ListItem");
  1872.         break;
  1873.       case "form":
  1874.         objStr = GetString("Form");
  1875.         break;
  1876.       case "input":
  1877.         var type = element.getAttribute("type");
  1878.         if (type && type.toLowerCase() == "image")
  1879.           objStr = GetString("InputImage");
  1880.         else
  1881.           objStr = GetString("InputTag");
  1882.         break;
  1883.       case "textarea":
  1884.         objStr = GetString("TextArea");
  1885.         break;
  1886.       case "select":
  1887.         objStr = GetString("Select");
  1888.         break;
  1889.       case "button":
  1890.         objStr = GetString("Button");
  1891.         break;
  1892.       case "label":
  1893.         objStr = GetString("Label");
  1894.         break;
  1895.       case "fieldset":
  1896.         objStr = GetString("FieldSet");
  1897.         break;
  1898.       case "a":
  1899.         if (element.name)
  1900.         {
  1901.           objStr = GetString("NamedAnchor");
  1902.           name = "anchor";
  1903.         }
  1904.         else if(element.href)
  1905.         {
  1906.           objStr = GetString("Link");
  1907.           name = "href";
  1908.         }
  1909.         break;
  1910.     }
  1911.     if (objStr)
  1912.       menuStr = GetString("ObjectProperties").replace(/%obj%/,objStr);
  1913.   }
  1914.   else
  1915.   {
  1916.     // We show generic "Properties" string, but disable menu item
  1917.     menuItem.setAttribute("disabled","true");
  1918.   }
  1919.   menuItem.setAttribute("label", menuStr);
  1920.   menuItem.setAttribute("accesskey",GetString("ObjectPropertiesAccessKey"));
  1921.   return name;
  1922. }
  1923.  
  1924. function InitParagraphMenu()
  1925. {
  1926.   var mixedObj = new Object();
  1927.   var state = editorShell.GetParagraphState(mixedObj);
  1928.   var IDSuffix;
  1929.  
  1930.   // PROBLEM: When we get blockquote, it masks other styles contained by it
  1931.   // We need a separate method to get blockquote state
  1932.  
  1933.   // We use "x" as uninitialized paragraph state
  1934.   if (!state || state == "x")
  1935.     IDSuffix = "bodyText" // No paragraph container
  1936.   else
  1937.     IDSuffix = state;
  1938.  
  1939.   // Set "radio" check on one item, but...
  1940.   var menuItem = document.getElementById("menu_"+IDSuffix);
  1941.   menuItem.setAttribute("checked", "true");
  1942.  
  1943.   // ..."bodyText" is returned if mixed selection, so remove checkmark
  1944.   if (mixedObj.value)
  1945.     menuItem.setAttribute("checked", "false");
  1946. }
  1947.  
  1948. function InitListMenu()
  1949. {
  1950.   var mixedObj = new Object();
  1951.   var state = editorShell.GetListState(mixedObj);
  1952.   var IDSuffix = "noList";
  1953.   if (state)
  1954.   {
  1955.     if (state == "dl")
  1956.       state = editorShell.GetListItemState(mixedObj);
  1957.  
  1958.     if (state)
  1959.       IDSuffix = state;
  1960.   }
  1961.   // Set enable state for the "None" menuitem
  1962.   goSetCommandEnabled("cmd_removeList", state);
  1963.  
  1964.   // Set "radio" check on one item, but...
  1965.   var menuItem = document.getElementById("menu_"+IDSuffix);
  1966.   if (!menuItem)
  1967.     return;
  1968.  
  1969.   menuItem.setAttribute("checked", "true");
  1970.  
  1971.   // ..."noList" is returned if mixed selection, so remove checkmark
  1972.   if (mixedObj.value)
  1973.     menuItem.setAttribute("checked", "false");
  1974.  
  1975. }
  1976.  
  1977. function InitAlignMenu()
  1978. {
  1979.   var mixedObj = new Object();
  1980.   // Note: GetAlignment DOESN'T set the "mixed" flag,
  1981.   //  but always returns "left" in mixed state
  1982.   // We'll pay attention to it here so it works when fixed
  1983.   var state = editorShell.GetAlignment(mixedObj);
  1984.   var IDSuffix;
  1985.  
  1986.   if (!state)
  1987.     IDSuffix = "left"
  1988.   else
  1989.     IDSuffix = state;
  1990.  
  1991.   var menuItem = document.getElementById("menu_"+IDSuffix);
  1992.   menuItem.setAttribute("checked", "true");
  1993.  
  1994.   if (mixedObj.value)
  1995.     menuItem.setAttribute("checked", "false");
  1996. }
  1997.  
  1998. function EditorInitToolbars()
  1999. {
  2000.   // Nothing to do now, but we might want some state updating here
  2001.   if (!IsEditorContentHTML())
  2002.   {
  2003.     //Hide the formating toolbar
  2004.     gFormatToolbar.setAttribute("hidden", "true");
  2005.  
  2006.     //Hide the edit mode toolbar
  2007.     gEditModeBar.setAttribute("hidden", "true");
  2008.     
  2009.     DisableItem("cmd_viewFormatToolbar", true);
  2010.     DisableItem("cmd_viewEditModeToolbar", true);
  2011.   }
  2012.  
  2013. }
  2014.  
  2015. function EditorSetDefaultPrefsAndDoctype()
  2016. {
  2017.   var domdoc;
  2018.   try { domdoc = window.editorShell.editorDocument; } catch (e) { dump( e + "\n"); }
  2019.   if ( !domdoc )
  2020.   {
  2021.     dump("EditorSetDefaultPrefsAndDoctype: EDITOR DOCUMENT NOT FOUND\n");
  2022.     return;
  2023.   }
  2024.  
  2025.   // Insert a doctype element 
  2026.   // if it is missing from existing doc
  2027.   if (!domdoc.doctype)
  2028.   {
  2029.     var newdoctype = domdoc.implementation.createDocumentType("html", "-//W3C//DTD HTML 4.01 Transitional//EN","");
  2030.     if (newdoctype)
  2031.       domdoc.insertBefore(newdoctype, domdoc.firstChild);
  2032.   }
  2033.   
  2034.   // search for head; we'll need this for meta tag additions
  2035.   var headelement = 0;
  2036.   var headnodelist = domdoc.getElementsByTagName("head");
  2037.   if (headnodelist)
  2038.   {
  2039.     var sz = headnodelist.length;
  2040.     if ( sz >= 1 )
  2041.       headelement = headnodelist.item(0);
  2042.   }
  2043.   else
  2044.   {
  2045.     headelement = domdoc.createElement("head");
  2046.     if (headelement)
  2047.       domdoc.insertAfter(headelement, domdoc.firstChild);
  2048.   }
  2049.  
  2050.   // add title tag if not present
  2051.   var titlenodelist = window.editorShell.editorDocument.getElementsByTagName("title");
  2052.   if (headelement && titlenodelist && titlenodelist.length == 0)
  2053.   {
  2054.      titleElement = domdoc.createElement("title");
  2055.      if (titleElement)
  2056.        headelement.appendChild(titleElement);
  2057.   }
  2058.  
  2059.   /* only set default prefs for new documents */
  2060.   if (!IsUrlAboutBlank(GetDocumentUrl()))
  2061.     return;
  2062.  
  2063.   // search for author meta tag.
  2064.   // if one is found, don't do anything.
  2065.   // if not, create one and make it a child of the head tag
  2066.   //   and set its content attribute to the value of the editor.author preference.
  2067.  
  2068.   var nodelist = domdoc.getElementsByTagName("meta");
  2069.   if ( nodelist )
  2070.   {
  2071.     // we should do charset first since we need to have charset before
  2072.     // hitting other 8-bit char in other meta tags
  2073.     // grab charset pref and make it the default charset
  2074.     var element;
  2075.     var prefCharsetString = 0;
  2076.     try
  2077.     {
  2078.       prefCharsetString = gPrefs.getComplexValue("intl.charset.default",
  2079.                                                  Components.interfaces.nsIPrefLocalizedString).data;
  2080.     }
  2081.     catch (ex) {}
  2082.     if ( prefCharsetString && prefCharsetString != 0)
  2083.     {
  2084.         element = domdoc.createElement("meta");
  2085.         if ( element )
  2086.         {
  2087.           AddAttrToElem(domdoc, "http-equiv", "content-type", element);
  2088.           AddAttrToElem(domdoc, "content", "text/html; charset=" + prefCharsetString, element);
  2089.           headelement.appendChild( element );
  2090.         }
  2091.     }
  2092.  
  2093.     var node = 0;
  2094.     var listlength = nodelist.length;
  2095.  
  2096.     // let's start by assuming we have an author in case we don't have the pref
  2097.     var authorFound = false;
  2098.     for (var i = 0; i < listlength && !authorFound; i++)
  2099.     {
  2100.       node = nodelist.item(i);
  2101.       if ( node )
  2102.       {
  2103.         var value = node.getAttribute("name");
  2104.         if (value && value.toLowerCase() == "author")
  2105.         {
  2106.           authorFound = true;
  2107.         }
  2108.       }
  2109.     }
  2110.  
  2111.     var prefAuthorString = 0;
  2112.     try
  2113.     {
  2114.       prefAuthorString = gPrefs.getComplexValue("editor.author",
  2115.                                                 Components.interfaces.nsISupportsWString).data;
  2116.     }
  2117.     catch (ex) {}
  2118.     if ( prefAuthorString && prefAuthorString != 0)
  2119.     {
  2120.       if ( !authorFound && headelement)
  2121.       {
  2122.         /* create meta tag with 2 attributes */
  2123.         element = domdoc.createElement("meta");
  2124.         if ( element )
  2125.         {
  2126.           AddAttrToElem(domdoc, "name", "author", element);
  2127.           AddAttrToElem(domdoc, "content", prefAuthorString, element);
  2128.           headelement.appendChild( element );
  2129.         }
  2130.       }
  2131.     }
  2132.   }
  2133.  
  2134.   // Get editor color prefs
  2135.   var use_custom_colors = false;
  2136.   try {
  2137.     use_custom_colors = gPrefs.getBoolPref("editor.use_custom_colors");
  2138.   }
  2139.   catch (ex) {}
  2140.  
  2141.   // find body node
  2142.   var bodyelement = GetBodyElement();
  2143.   if (bodyelement)
  2144.   {
  2145.     if ( use_custom_colors )
  2146.     {
  2147.       // try to get the default color values.  ignore them if we don't have them.
  2148.       var text_color;
  2149.       var link_color;
  2150.       var active_link_color;
  2151.       var followed_link_color;
  2152.       var background_color;
  2153.  
  2154.       try { text_color = gPrefs.getCharPref("editor.text_color"); } catch (e) {}
  2155.       try { link_color = gPrefs.getCharPref("editor.link_color"); } catch (e) {}
  2156.       try { active_link_color = gPrefs.getCharPref("editor.active_link_color"); } catch (e) {}
  2157.       try { followed_link_color = gPrefs.getCharPref("editor.followed_link_color"); } catch (e) {}
  2158.       try { background_color = gPrefs.getCharPref("editor.background_color"); } catch(e) {}
  2159.  
  2160.       // add the color attributes to the body tag.
  2161.       // and use them for the default text and background colors if not empty
  2162.       if (text_color)
  2163.       {
  2164.         bodyelement.setAttribute("text", text_color);
  2165.         gDefaultTextColor = text_color;
  2166.       }
  2167.       if (background_color)
  2168.       {
  2169.         bodyelement.setAttribute("bgcolor", background_color);
  2170.         gDefaultBackgroundColor = background_color
  2171.       }
  2172.  
  2173.       if (link_color)
  2174.         bodyelement.setAttribute("link", link_color);
  2175.       if (active_link_color)
  2176.         bodyelement.setAttribute("alink", active_link_color);
  2177.       if (followed_link_color)
  2178.         bodyelement.setAttribute("vlink", followed_link_color);
  2179.     }
  2180.     // Default image is independent of Custom colors???
  2181.     var background_image;
  2182.     try { background_image = gPrefs.getCharPref("editor.default_background_image"); } catch(e) {}
  2183.  
  2184.     if (background_image)
  2185.       bodyelement.setAttribute("background", background_image);
  2186.   }
  2187.   // auto-save???
  2188. }
  2189.  
  2190. function GetBodyElement()
  2191. {
  2192.   try {
  2193.     var bodyNodelist = window.editorShell.editorDocument.getElementsByTagName("body");
  2194.     if (bodyNodelist)
  2195.       return bodyNodelist.item(0);
  2196.   }
  2197.   catch (ex) {
  2198.     dump("no body tag found?!\n");
  2199.     //  better have one, how can we blow things up here?
  2200.   }
  2201.   return null;
  2202. }
  2203.  
  2204. function AddAttrToElem(dom, attr_name, attr_value, elem)
  2205. {
  2206.   var a = dom.createAttribute(attr_name);
  2207.   if ( a )
  2208.   {
  2209.     a.value = attr_value;
  2210.     elem.setAttributeNode(a);
  2211.   }
  2212. }
  2213.  
  2214. // --------------------------- Logging stuff ---------------------------
  2215.  
  2216. function EditorGetNodeFromOffsets(offsets)
  2217. {
  2218.   var node = null;
  2219.   node = editorShell.editorDocument;
  2220.  
  2221.   for (var i = 0; i < offsets.length; i++)
  2222.   {
  2223.     node = node.childNodes[offsets[i]];
  2224.   }
  2225.  
  2226.   return node;
  2227. }
  2228.  
  2229. function EditorSetSelectionFromOffsets(selRanges)
  2230. {
  2231.   var rangeArr, start, end, node, offset;
  2232.   var selection = editorShell.editorSelection;
  2233.  
  2234.   selection.removeAllRanges();
  2235.  
  2236.   for (var i = 0; i < selRanges.length; i++)
  2237.   {
  2238.     rangeArr = selRanges[i];
  2239.     start    = rangeArr[0];
  2240.     end      = rangeArr[1];
  2241.  
  2242.     var range = editorShell.editorDocument.createRange();
  2243.  
  2244.     node   = EditorGetNodeFromOffsets(start[0]);
  2245.     offset = start[1];
  2246.  
  2247.     range.setStart(node, offset);
  2248.  
  2249.     node   = EditorGetNodeFromOffsets(end[0]);
  2250.     offset = end[1];
  2251.  
  2252.     range.setEnd(node, offset);
  2253.  
  2254.     selection.addRange(range);
  2255.   }
  2256. }
  2257.  
  2258. //--------------------------------------------------------------------
  2259. function initFontStyleMenu(menuPopup)
  2260. {
  2261.   for (var i = 0; i < menuPopup.childNodes.length; i++)
  2262.   {
  2263.     var menuItem = menuPopup.childNodes[i];
  2264.     var theStyle = menuItem.getAttribute("state");
  2265.     if (theStyle)
  2266.     {
  2267.       menuItem.setAttribute("checked", theStyle);
  2268.     }
  2269.   }
  2270. }
  2271.  
  2272. //--------------------------------------------------------------------
  2273. function onButtonUpdate(button, commmandID)
  2274. {
  2275.   var commandNode = document.getElementById(commmandID);
  2276.   var state = commandNode.getAttribute("state");
  2277.   button.checked = state == "true";
  2278. }
  2279.  
  2280. //--------------------------------------------------------------------
  2281. function onStateButtonUpdate(button, commmandID, onState)
  2282. {
  2283.   var commandNode = document.getElementById(commmandID);
  2284.   var state = commandNode.getAttribute("state");
  2285.  
  2286.   button.checked = state == onState;
  2287. }
  2288.  
  2289. // --------------------------- Status calls ---------------------------
  2290. function getColorAndSetColorWell(ColorPickerID, ColorWellID)
  2291. {
  2292.   var colorWell;
  2293.   if (ColorWellID)
  2294.     colorWell = document.getElementById(ColorWellID);
  2295.  
  2296.   var colorPicker = document.getElementById(ColorPickerID);
  2297.   if (colorPicker)
  2298.   {
  2299.     // Extract color from colorPicker and assign to colorWell.
  2300.     var color = colorPicker.getAttribute("color");
  2301.  
  2302.     if (colorWell && color)
  2303.     {
  2304.       // Use setAttribute so colorwell can be a XUL element, such as button
  2305.       colorWell.setAttribute("style", "background-color: " + color);
  2306.     }
  2307.   }
  2308.   return color;
  2309. }
  2310.  
  2311. //-----------------------------------------------------------------------------------
  2312. function IsSpellCheckerInstalled()
  2313. {
  2314.   return "@mozilla.org/spellchecker;1" in Components.classes;
  2315. }
  2316.  
  2317. //-----------------------------------------------------------------------------------
  2318. function IsFindInstalled()
  2319. {
  2320.   return "@mozilla.org/appshell/component/find;1" in Components.classes;
  2321. }
  2322.  
  2323. //-----------------------------------------------------------------------------------
  2324. function RemoveInapplicableUIElements()
  2325. {
  2326.   // For items that are in their own menu block, remove associated separator
  2327.   // (we can't use "hidden" since class="hide-in-IM" CSS rule interferes)
  2328.  
  2329.    // if no find, remove find ui
  2330.   if (!IsFindInstalled())
  2331.   {
  2332.     HideItem("menu_find");
  2333.     HideItem("menu_findnext");
  2334.     HideItem("menu_replace");
  2335.     HideItem("menu_find");
  2336.     RemoveItem("sep_find");
  2337.   }
  2338.  
  2339.    // if no spell checker, remove spell checker ui
  2340.   if (!IsSpellCheckerInstalled())
  2341.   {
  2342.     HideItem("spellingButton");
  2343.     HideItem("menu_checkspelling");
  2344.     RemoveItem("sep_checkspelling");
  2345.   }
  2346.   else
  2347.   {
  2348.     SetElementEnabled(document.getElementById("menu_checkspelling"), true);
  2349.     SetElementEnabled(document.getElementById("spellingButton"), true);
  2350.     SetElementEnabled(document.getElementById("checkspellingkb"), true);
  2351.   }
  2352.  
  2353.   // Remove menu items (from overlay shared with HTML editor) in PlainText editor
  2354.   if (editorShell.editorType == "text")
  2355.   {
  2356.     HideItem("insertAnchor");
  2357.     HideItem("insertImage");
  2358.     HideItem("insertHline");
  2359.     HideItem("insertTable");
  2360.     HideItem("insertHTML");
  2361.     HideItem("insertFormMenu");
  2362.     HideItem("fileExportToText");
  2363.     HideItem("viewFormatToolbar");
  2364.     HideItem("viewEditModeToolbar");
  2365.   }
  2366. }
  2367.  
  2368. function HideItem(id)
  2369. {
  2370.   var item = document.getElementById(id);
  2371.   if (item)
  2372.     item.setAttribute("hidden", "true");
  2373. }
  2374.  
  2375. function RemoveItem(id)
  2376. {
  2377.   var item = document.getElementById(id);
  2378.   if (item)
  2379.     item.parentNode.removeChild(item);
  2380. }
  2381.  
  2382. // Command Updating Strategy:
  2383. //   Don't update on on selection change, only when menu is displayed,
  2384. //   with this "oncreate" hander:
  2385. function EditorInitTableMenu()
  2386. {
  2387.   try {
  2388.     InitJoinCellMenuitem("menu_JoinTableCells");
  2389.   } catch (ex) {}
  2390.  
  2391.   // Set enable states for all table commands
  2392.   goUpdateTableMenuItems(document.getElementById("composerTableMenuItems"));
  2393. }
  2394.  
  2395. function InitJoinCellMenuitem(id)
  2396. {
  2397.   // Change text on the "Join..." item depending if we
  2398.   //   are joining selected cells or just cell to right
  2399.   // TODO: What to do about normal selection that crosses
  2400.   //       table border? Try to figure out all cells
  2401.   //       included in the selection?
  2402.   var menuText;
  2403.   var menuItem = document.getElementById(id);
  2404.   if (!menuItem) return;
  2405.  
  2406.   // Use "Join selected cells if there's more than 1 cell selected
  2407.   var tagNameObj = new Object;
  2408.   var countObj = new Object;
  2409.   var foundElement;
  2410.   
  2411.   if (IsEditingRenderedHTML())
  2412.     foundElement = window.editorShell.GetSelectedOrParentTableElement(tagNameObj, countObj);
  2413.     
  2414.   if (foundElement && countObj.value > 1)
  2415.     menuText = GetString("JoinSelectedCells");
  2416.   else
  2417.     menuText = GetString("JoinCellToRight");
  2418.  
  2419.   menuItem.setAttribute("label",menuText);
  2420.   menuItem.setAttribute("accesskey",GetString("JoinCellAccesskey"));
  2421. }
  2422.  
  2423. function InitRemoveStylesMenuitems(removeStylesId, removeLinksId, removeNamedAnchorsId)
  2424. {
  2425.   // Change wording of menuitems depending on selection
  2426.   var stylesItem = document.getElementById(removeStylesId);
  2427.   var linkItem = document.getElementById(removeLinksId);
  2428.   var namedAnchorsItem = document.getElementById(removeNamedAnchorsId);
  2429.  
  2430.   var isCollapsed = editorShell.editorSelection.isCollapsed;
  2431.   if (stylesItem)
  2432.   {
  2433.     stylesItem.setAttribute("label", isCollapsed ? GetString("StopTextStyles") : GetString("RemoveTextStyles"));
  2434.     stylesItem.setAttribute("accesskey", GetString("RemoveTextStylesAccesskey"));
  2435.   }
  2436.   if (linkItem)
  2437.   {
  2438.     linkItem.setAttribute("label", isCollapsed ? GetString("StopLinks") : GetString("RemoveLinks"));
  2439.     linkItem.setAttribute("accesskey", GetString("RemoveLinksAccesskey"));
  2440.     // Note: disabling text style is a pain since there are so many - forget it!
  2441.  
  2442.     // Disable if not in a link, but always allow "Remove"
  2443.     //  if selection isn't collapsed since we only look at anchor node
  2444.     DisableItem(removeLinksId, isCollapsed && !window.editorShell.GetElementOrParentByTagName("href", null));
  2445.   }
  2446.   if (namedAnchorsItem)
  2447.   {
  2448.     // Disable if selection is collapsed
  2449.     DisableItem(removeNamedAnchorsId, isCollapsed);
  2450.   }
  2451. }
  2452.  
  2453. function goUpdateTableMenuItems(commandset)
  2454. {
  2455.   var enabled = false;
  2456.   var enabledIfTable = false;
  2457.  
  2458.   if (window.editorShell && window.editorShell.documentEditable && IsEditingRenderedHTML())
  2459.   {
  2460.     var selectedCountObj = new Object();
  2461.     var tagNameObj = new Object();
  2462.     var element = editorShell.GetSelectedOrParentTableElement(tagNameObj, selectedCountObj);
  2463.     if (element)
  2464.     {
  2465.       // Value when we need to have a selected table or inside a table
  2466.       enabledIfTable = true;
  2467.  
  2468.       // All others require being inside a cell or selected cell
  2469.       enabled = (tagNameObj.value == "td");
  2470.     }
  2471.   }
  2472.  
  2473.   // Loop through command nodes
  2474.   for (var i = 0; i < commandset.childNodes.length; i++)
  2475.   {
  2476.     var commandID = commandset.childNodes[i].getAttribute("id");
  2477.     if (commandID)
  2478.     {
  2479.       if (commandID == "cmd_InsertTable" ||
  2480.           commandID == "cmd_JoinTableCells" ||
  2481.           commandID == "cmd_SplitTableCell" ||
  2482.           commandID == "cmd_ConvertToTable")
  2483.       {
  2484.         // Call the update method in the command class
  2485.         goUpdateCommand(commandID);
  2486.       }
  2487.       // Directly set with the values calculated here
  2488.       else if (commandID == "cmd_DeleteTable" ||
  2489.                commandID == "cmd_NormalizeTable" ||
  2490.                commandID == "cmd_editTable" ||
  2491.                commandID == "cmd_TableOrCellColor" ||
  2492.                commandID == "cmd_SelectTable")
  2493.       {
  2494.         goSetCommandEnabled(commandID, enabledIfTable);
  2495.       } else {
  2496.         goSetCommandEnabled(commandID, enabled);
  2497.       }
  2498.     }
  2499.   }
  2500. }
  2501.  
  2502. //-----------------------------------------------------------------------------------
  2503. // Helpers for inserting and editing tables:
  2504.  
  2505. function IsInTable()
  2506. {
  2507.   return (window.editorShell && window.editorShell.documentEditable && IsEditingRenderedHTML() &&
  2508.           null != window.editorShell.GetElementOrParentByTagName("table", null));
  2509. }
  2510.  
  2511. function IsInTableCell()
  2512. {
  2513.   return (window.editorShell && window.editorShell.documentEditable && IsEditingRenderedHTML() &&
  2514.           null != window.editorShell.GetElementOrParentByTagName("td", null));
  2515. }
  2516.  
  2517. function IsSelectionInOneCell()
  2518. {
  2519.   var selection = window.editorShell.editorSelection;
  2520.  
  2521.   if (selection && selection.rangeCount == 1)
  2522.   {
  2523.     // We have a "normal" single-range selection
  2524.     if (!selection.isCollapsed &&
  2525.        selection.anchorNode != selection.focusNode)
  2526.     {
  2527.       // Check if both nodes are within the same cell
  2528.       var anchorCell = window.editorShell.GetElementOrParentByTagName("td", selection.anchorNode);
  2529.       var focusCell = window.editorShell.GetElementOrParentByTagName("td", selection.focusNode);
  2530.       return (focusCell != null && anchorCell != null && (focusCell == anchorCell));
  2531.     }
  2532.     // Collapsed selection or anchor == focus (thus must be in 1 cell)
  2533.     return true;
  2534.   }
  2535.   return false;
  2536. }
  2537.  
  2538. // Call this with insertAllowed = true to allow inserting if not in existing table,
  2539. //   else use false to do nothing if not in a table
  2540. function EditorInsertOrEditTable(insertAllowed)
  2541. {
  2542.   if (IsInTable()) {
  2543.     // Edit properties of existing table
  2544.     window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "","TablePanel");
  2545.     gContentWindow.focus();
  2546.   } else if(insertAllowed) {
  2547.     if (editorShell.editorSelection.isCollapsed)
  2548.       // If we have a caret, insert a blank table...
  2549.       EditorInsertTable();
  2550.     else
  2551.       // else convert the selection into a table
  2552.       goDoCommand("cmd_ConvertToTable");
  2553.   }
  2554. }
  2555.  
  2556. function EditorInsertTable()
  2557. {
  2558.   // Insert a new table
  2559.   window.openDialog("chrome://editor/content/EdInsertTable.xul", "_blank", "chrome,close,titlebar,modal", "");
  2560.   gContentWindow.focus();
  2561. }
  2562.  
  2563. function EditorTableCellProperties()
  2564. {
  2565.   var cell = editorShell.GetElementOrParentByTagName("td", null);
  2566.   if (cell) {
  2567.     // Start Table Properties dialog on the "Cell" panel
  2568.     window.openDialog("chrome://editor/content/EdTableProps.xul", "_blank", "chrome,close,titlebar,modal", "", "CellPanel");
  2569.     gContentWindow.focus();
  2570.   }
  2571. }
  2572.  
  2573. function GetNumberOfContiguousSelectedRows()
  2574. {
  2575.   var cell = editorShell.GetFirstSelectedCell();
  2576.  
  2577.   if (!cell)
  2578.     return 0;
  2579.  
  2580.   var rows = 1;
  2581.   var lastIndex = editorShell.GetRowIndex(cell);
  2582.  
  2583.   do {
  2584.     cell = editorShell.GetNextSelectedCell();
  2585.     if (cell)
  2586.     {
  2587.       var index = editorShell.GetRowIndex(cell);
  2588.       if (index == lastIndex + 1)
  2589.       {
  2590.         lastIndex = index;
  2591.         rows++;
  2592.       }
  2593.     }
  2594.   }
  2595.   while (cell);
  2596.  
  2597.   return rows;
  2598. }
  2599.  
  2600. function GetNumberOfContiguousSelectedColumns()
  2601. {
  2602.   var cell = editorShell.GetFirstSelectedCell();
  2603.  
  2604.   if (!cell)
  2605.     return 0;
  2606.  
  2607.   var columns = 1;
  2608.   var lastIndex = editorShell.GetColumnIndex(cell);
  2609.  
  2610.   do {
  2611.     cell = editorShell.GetNextSelectedCell();
  2612.     if (cell)
  2613.     {
  2614.       var index = editorShell.GetColumnIndex(cell);
  2615.       if (index == lastIndex +1)
  2616.       {
  2617.         lastIndex = index;
  2618.         columns++;
  2619.       }
  2620.     }
  2621.   }
  2622.   while (cell);
  2623.  
  2624.   return columns;
  2625. }
  2626.  
  2627. function EditorOnFocus()
  2628. {
  2629.   // Current window already has the InsertCharWindow
  2630.   if ("InsertCharWindow" in window && window.InsertCharWindow) return;
  2631.  
  2632.   // Find window with an InsertCharsWindow and switch association to this one
  2633.   var windowWithDialog = FindEditorWithInsertCharDialog();
  2634.   if (windowWithDialog)
  2635.   {
  2636.     // Switch the dialog to current window
  2637.     // this sets focus to dialog, so bring focus back to editor window
  2638.     if (SwitchInsertCharToThisWindow(windowWithDialog))
  2639.       window.focus();
  2640.   }
  2641. }
  2642.  
  2643. function SwitchInsertCharToThisWindow(windowWithDialog)
  2644. {
  2645.   if (windowWithDialog && "InsertCharWindow" in windowWithDialog &&
  2646.       windowWithDialog.InsertCharWindow)
  2647.   {
  2648.     // Move dialog association to the current window
  2649.     window.InsertCharWindow = windowWithDialog.InsertCharWindow;
  2650.     windowWithDialog.InsertCharWindow = null;
  2651.  
  2652.     // Switch the dialog's editorShell and opener to current window's
  2653.     window.InsertCharWindow.editorShell = window.editorShell;
  2654.     window.InsertCharWindow.opener = window;
  2655.  
  2656.     // Bring dialog to the forground
  2657.     window.InsertCharWindow.focus();
  2658.     return true;
  2659.   }
  2660.   return false;
  2661. }
  2662.  
  2663. function FindEditorWithInsertCharDialog()
  2664. {
  2665.   // Find window with an InsertCharsWindow and switch association to this one
  2666.   var windowManager = Components.classes['@mozilla.org/rdf/datasource;1?name=window-mediator'].getService();
  2667.   var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
  2668.   var enumerator = windowManagerInterface.getEnumerator( null );
  2669.  
  2670.   while ( enumerator.hasMoreElements()  )
  2671.   {
  2672.     var  tempWindow = enumerator.getNext();
  2673.  
  2674.     if (tempWindow != window && "InsertCharWindow" in tempWindow &&
  2675.         tempWindow.InsertCharWindow)
  2676.     {
  2677.       return tempWindow;
  2678.     }
  2679.   }
  2680.   return null;
  2681. }
  2682.  
  2683. function EditorFindOrCreateInsertCharWindow()
  2684. {
  2685.   if ("InsertCharWindow" in window && window.InsertCharWindow)
  2686.     window.InsertCharWindow.focus();
  2687.   else
  2688.   {
  2689.     // Since we switch the dialog during EditorOnFocus(),
  2690.     //   this should really never be found, but it's good to be sure
  2691.     var windowWithDialog = FindEditorWithInsertCharDialog();
  2692.     if (windowWithDialog)
  2693.     {
  2694.       SwitchInsertCharToThisWindow(windowWithDialog);
  2695.     }
  2696.     else
  2697.     {
  2698.       // The dialog will set window.InsertCharWindow to itself
  2699.       window.openDialog("chrome://editor/content/EdInsertChars.xul", "_blank", "chrome,close,titlebar", "");
  2700.     }
  2701.   }
  2702. }
  2703.  
  2704. // Find another HTML editor window to associate with the InsertChar dialog
  2705. //   or close it if none found  (May be a mail composer)
  2706. function SwitchInsertCharToAnotherEditorOrClose()
  2707. {
  2708.   if ("InsertCharWindow" in window && window.InsertCharWindow)
  2709.   {
  2710.     var windowManager = Components.classes['@mozilla.org/rdf/datasource;1?name=window-mediator'].getService();
  2711.     var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
  2712.     var enumerator = windowManagerInterface.getEnumerator( null );
  2713.  
  2714.     // TODO: Fix this to search for command controllers and look for "cmd_InsertChars"
  2715.     // For now, detect just Web Composer and HTML Mail Composer
  2716.     while ( enumerator.hasMoreElements()  )
  2717.     {
  2718.       var  tempWindow = enumerator.getNext();
  2719.       if (tempWindow != window && tempWindow != window.InsertCharWindow && 
  2720.           ("editorShell" in tempWindow) && tempWindow.editorShell)
  2721.       {
  2722.         var type = tempWindow.editorShell.editorType;
  2723.         if (type == "html" || type == "text" || type == "htmlmail")
  2724.           {
  2725.           tempWindow.InsertCharWindow = window.InsertCharWindow;
  2726.           window.InsertCharWindow = null;
  2727.  
  2728.           tempWindow.InsertCharWindow.editorShell = tempWindow.editorShell;
  2729.           tempWindow.InsertCharWindow.opener = tempWindow;
  2730.           return;
  2731.           }
  2732.       }
  2733.     }
  2734.     // Didn't find another editor - close the dialog
  2735.     window.InsertCharWindow.close();
  2736.   }
  2737. }
  2738.